Compono.XunitV3¶
xUnit v3 integration — theory data attributes that compose test parameters directly, instead of hand-building [MemberData] rows or a custom AutoDataAttribute wrapper.
When to install¶
You write xUnit v3 tests (xunit.v3 + the Microsoft Testing Platform runner) and want theory parameters composed automatically:
Compono.XunitV3 doesn't add an xUnit v3 test host for you — it integrates with an existing one. If your project targets NUnit, MSTest, or plain xUnit v3 without composed data, you don't need this package; core Compono still works standalone via Composer.Create() and the resulting composer's own Create<T>().
What it gives you¶
[Compose]— every theory parameter is composed. See Your First Composed Theory.[Compose<TProfile>]— same, with a specificICompositionProfileapplied.[Compose<TProfile, TConfig>]— same, with a profile built from call-site-known profile configuration arguments — see Profile configuration arguments below.- Inline + composed mixing —
[Compose(42, "widget")]binds inline values left-to-right; anything left over is composed. See How Do I Write a Composed Theory?. [Shared]— reuse one composed instance across every parameter (or nested dependency) in the same row that requests the same type. See Shared Values.Compose(Seed = ...)— reproduce a specific composed row exactly; every row is also tagged with aCompono.Seedtrait, and a composition failure's message includes the seed that produced it. For an assertion failure in the test body — composition succeeded, but the test itself failed — the message won't have it; use theCompono.Seedtrait instead. See Determinism and Seeding.
Profile configuration arguments¶
[Compose<TProfile>] selects a fixed, default-constructed profile type — the same profile, configured the same way, for every caller. When a profile needs to be built differently per test call site (drawn from real migration evidence — see Migrating from AutoFixture), [Compose<TProfile, TConfig>] binds this attribute's own constructor arguments — profile configuration arguments, a distinct concept from this package's inline values above — positionally to TConfig's single public constructor, then constructs TProfile from that TConfig:
public enum RepositoryKind
{
Player,
Game,
}
public sealed record RepositoryConfig(RepositoryKind Repository);
public sealed class RepositoryProfile : ICompositionProfile
{
public RepositoryProfile(RepositoryConfig config) => Config = config;
public RepositoryConfig Config { get; }
public void Configure(CompositionBuilder builder) =>
builder.Register<IRepository>(_ => RepositoryFactory.Create(Config.Repository));
}
[Theory]
[Compose<RepositoryProfile, RepositoryConfig>(RepositoryKind.Player)]
public void Handles_PlayerRepository(IRepository repository) { }
Inline values vs. profile configuration arguments — never the same thing. Inline values ([Compose(42, "widget")]) bind to the test method's own parameters. Profile configuration arguments ([Compose<TProfile, TConfig>(...)]) bind to TConfig's constructor, which builds the profile applied to the whole row — they never bind to the test method's parameters, all of which are composed in full under this attribute form.
Prefer the strongest attribute-legal type for each argument. params object?[] is a binding mechanism forced by C#'s attribute-argument-must-be-a-compile-time-constant rule, not a license to design TConfig around magic strings — use an enum for a finite choice (RepositoryKind.Player, not "Player"), typeof(...) for a CLR type, a bool/numeric value where that's already the real meaning.
Constructor contracts are narrow and deterministic, not "best match." TConfig must have exactly one public constructor; TProfile must have exactly one public constructor accepting exactly one TConfig-typed parameter. Either shape being missing or ambiguous is a clear, cached CompositionException — computed once per attribute instance, never on the per-row path. See Troubleshooting: Common Errors for each specific message. This is a deliberate tradeoff: [Compose<TProfile>]'s TProfile : ICompositionProfile, new() constraint rejects an invalid profile type at compile time; this form's constructor-shape checks can only happen at runtime, since "has a constructor accepting exactly this type" isn't expressible as a C# generic constraint.
What it deliberately doesn't do¶
- No stacking distinct Compose-family attributes on one method. A test needing several inline rows plus composed parameters in each — the AutoFixture idiom of stacking multiple
[InlineAutoData(...)]instances — has no direct equivalent here. Two different Compose-family attribute types (e.g.[Compose]and[Compose<ProfileA>]) compile without complaint, butBindingPlan.ValidateSignaturethrows aCompositionExceptionat data-binding time once it sees more than one applied to the same method. Only the exact same closed attribute type twice is a compiler error (AllowMultiple = false). If you need this shape, pick one Compose-family attribute per method and supply the varying rows another way (e.g. inline[Theory]/[InlineData]rows for the parts that need no composition, per Migrating from AutoFixture). - No fixture object. There's nothing analogous to AutoFixture's
IFixture— configuration lives in a profile ([Compose<TProfile>]), applied per test method, not a shared mutable object.
Next¶
- Share a Value Across a Test
- Use Profiles
- Migrating from AutoFixture — the full
AutoDataAttribute/InlineAutoDataAttributemapping, from a real migration.