Skip to content

feat(nsubstitute): milestone 5 phase 2 - test suites and verification - #30

Merged
ncipollina merged 1 commit into
mainfrom
feat/nsubstitute-milestone-5-phase-2
Jul 31, 2026
Merged

feat(nsubstitute): milestone 5 phase 2 - test suites and verification#30
ncipollina merged 1 commit into
mainfrom
feat/nsubstitute-milestone-5-phase-2

Conversation

@ncipollina

Copy link
Copy Markdown
Contributor

📋 Summary

Implements PLAN-0005 Phase 2: test coverage for Compono.NSubstitute (Phase 0/1's engine extension point and provider package) and a real, packaged end-to-end verification. Writing this phase's own regression coverage caught two real bugs in already-merged Phase 1/Milestone-1 code, both fixed here with their own regression tests.


📝 Changes

test/Compono.NSubstitute.Tests (new project, 23 tests × 2 TFMs)

  • IsSubstitutableTests.cs — direct coverage of NSubstituteProvider.IsSubstitutable (interface / delegate / abstract-class with the option on and off / sealed class / struct / string), including the System.Delegate/System.MulticastDelegate negative cases ADR-0025 Amendment 1 calls out.
  • NSubstituteProviderTests.csTryProvide's outcome exercised through a real Composer (CompositionProviderResult's Value/IsHandled are internal to Compono, so this is the only way to observe it from outside), plus the options-mutation snapshot regression test.
  • CompositionBuilderExtensionsTests.csUseNSubstitute()/UseNSubstitute(configure) wiring a working provider into a real Composer.
  • EndToEndCompositionTests.cs — the plan's own [Shared]-via-CompositionRow reuse shape, run against the real NSubstituteProvider.
  • PublicApiSurfaceTests.cs — locks Compono.NSubstitute's exact public type set, matching Compono.XunitV3.Tests' existing pattern.

Real end-to-end verification (test/Compono.XunitV3.SampleTests)

  • Added a Compono.NSubstitute package reference (packed from current source via pack-to-local-feed.sh, not a ProjectReference) and NSubstituteTests.cs, running the plan's own Goal-section scenario verbatim under a real xUnit v3 runner.

Two real bugs found and fixed while writing this phase's tests:

  1. NSubstituteProvider.IsSubstitutable misclassified System.Delegate/System.MulticastDelegate themselves as substitutable abstract classes — ADR-0025 Amendment 1's stated intent was only half-enforced. Fixed by excluding typeof(Delegate).IsAssignableFrom(requestedType) from the abstract-class branch.
  2. The real end-to-end run exposed a CMP0003 compile error on the plan's own Goal snippet ([Shared] IOrderRepository as a bare xUnit theory parameter): Compono.Generators' LeafTypeClassifier.IsRuntimeProviderResolved still hard-rejected an interface/abstract-class/delegate root, a stale pre-Milestone-5 assumption from before public providers existed. Loosened to match the member-level classification (ADR-0024 Amendment 2), with four rewritten and one new Compono.Generators.Tests snapshot test proving concrete-type roots are unaffected.

Docs

  • docs/adr/0024-public-provider-extensibility-model.md — Amendment 2 records the CMP0003 root-check change and why it ships as an ordinary fix, not breaking.
  • docs/mvp.md, docs/public-api.md — Milestone 5 / NSubstitute Integration now say "implemented and test-covered/end-to-end verified."
  • docs/plans/0005-milestone-5-nsubstitute-integration.md — Phase 2 checked off and closed out with a detailed Notes entry.

🧪 Validation

  • Build/test status: whole-solution dotnet build/dotnet test720/720 passing (Compono.Tests 412, Compono.Generators.Tests 168, Compono.XunitV3.Tests 94, Compono.NSubstitute.Tests 46, all ×2 TFMs), 0 warnings.
  • Manual verification performed: separate real dotnet test run of test/Compono.XunitV3.SampleTests against packages packed fresh from current source (not ProjectReference) — NSubstituteTests.Saves_order passes end-to-end.
  • Edge cases checked: Delegate/MulticastDelegate framework base types vs. a real custom delegate type; abstract-class substitution with the option on/off; options-instance mutation after registration has no effect on an already-registered provider; a concrete sealed type with no accessible constructor still reports CMP0002 at a composed root (proving the CMP0003 loosening is scoped correctly).

💬 Notes for Reviewers

The CMP0003 change (item 2 above) is the part most worth a close look — it's a generator behavior change, not test-only, made after confirming the direction with the repo owner rather than unilaterally. It's additive (previously non-compiling code now compiles; nothing that compiled before changes meaning), so no breaking label applied, per ADR-0024's own Alpha Compatibility Policy.

PLAN-0005 Phase 3 (docs/cleanup) remains open as its own follow-up PR.

Adds Compono.NSubstitute.Tests (IsSubstitutable/NSubstituteProvider/
UseNSubstitute coverage, shared-substitute reuse via CompositionRow, and a
public-API-surface approval test) and a real packaged end-to-end run through
Compono.XunitV3.SampleTests proving the plan's own Goal scenario compiles
and passes under a real xUnit v3 runner.

Writing this phase's own regression coverage caught two real bugs before
they shipped further:

- NSubstituteProvider.IsSubstitutable misclassified System.Delegate/
  System.MulticastDelegate themselves as substitutable abstract classes -
  ADR-0025 Amendment 1's stated intent was only half-enforced.
- Compono.Generators' root-level CMP0003 check still hard-rejected an
  interface/abstract-class/delegate type used as a composed root, a stale
  pre-Milestone-5 assumption that broke this plan's own Goal snippet when
  actually compiled. Loosened per ADR-0024 Amendment 2, with generator
  regression coverage proving concrete-type roots are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the feat label Jul 31, 2026
@ncipollina
ncipollina merged commit acc0e64 into main Jul 31, 2026
7 checks passed
@ncipollina
ncipollina deleted the feat/nsubstitute-milestone-5-phase-2 branch July 31, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant