Compono.NSubstitute¶
Automatic substitute composition — interface, delegate, and (optionally) abstract-class parameters compose to real NSubstitute substitutes instead of failing with "no accessible constructor."
When to install¶
Your composed types depend on interfaces (or delegates, or unsealed abstract classes) that you'd otherwise create with Substitute.For<T>() by hand:
If none of your composed types have interface/delegate/abstract-class dependencies, you don't need this package.
What it gives you¶
var composer = Composer.Create(builder => builder.UseNSubstitute());
var handler = composer.Create<CreateOrderHandler>();
// handler's IOrderRepository dependency is a real NSubstitute substitute
UseNSubstitute()— registers the substitute provider as a pipeline stage. Once active, any interface or delegate-typed parameter composes toSubstitute.For<T>()automatically — no per-type registration needed.SubstituteAbstractClasses(NSubstituteOptions, defaulttrue) — also substitutes unsealed abstract classes, not just interfaces and delegates. Set tofalseviaUseNSubstitute(o => o.SubstituteAbstractClasses = false)if you want the provider to decline abstract-class requests instead. Abstract types are always provider-resolved — an abstract class never falls back to ordinary constructor selection even when the provider declines it — so composing an abstract class with this option off throwsCompositionExceptionrather than constructing it directly.- Combine with
[Shared](Compono.XunitV3) to assert against, or configure, the exact substitute instance a composed dependency received — see Shared Values.
What it deliberately doesn't do¶
No member auto-configuration. Every substitute is a bare Substitute.For<T>() — Compono never pre-configures a member's return value (no AutoFixture-style ConfigureMembers = true equivalent). An unstubbed call on a Task<T>-returning member returns NSubstitute's own default (Task.FromResult<T>(default)), not a recursively-composed value. If your code depends on a specific return value, stub it explicitly:
This is a deliberate design choice, not a missing feature — an implicit auto-configured return value hides a test's true dependency on that value's shape. See Migrating from AutoFixture for a real case where this surfaced a genuine hidden dependency during migration, and ADR-0025 for the full rationale.
Next¶
- Shared Values — asserting against a composed substitute.
- Providers — where the substitute provider sits in the resolution pipeline.