Environment conditions, .NET 10 support, and xUnit v3 3.2.2 - #33
Merged
Conversation
Conditions worked on services and nowhere else. A condition on a [Decorator]
compiled, read as deliberate, and did nothing at all -- decoration never looked
at them, so a decorator marked [IfEnvironment("Development")] wrapped the service
in production too. And a convention could only inherit conditions from the classes
it matched, so gating a whole rule meant repeating the attribute on every class.
Decorators now carry conditions the way services do. DecoratorModel gained them,
DecoratorModelUtility reads them off the class, and the writer guards the Decorate
call rather than the registration -- a decorator that does not apply is simply
never run, so the service resolves undecorated instead of being wrapped by
something that re-tests the environment on every call. Ordering is untouched: a
condition decides whether a decorator applies, never where it sits.
Reaching the environment at decoration time needed DecoratorRegistration to hold
an EnvironmentRegistryFunc, mirroring what RegistryFunc/EnvironmentRegistryFunc
already do for services. Unconditional decorators keep the old shape and are
adapted, so conditional and unconditional ones still sort against each other on
Order alone. ApplyDecorators finds the environment already registered by
ApplyServices rather than resolving a second answer to "what environment is this".
Conventions gained IfEnvironment, IfNotEnvironment, IfEnvironmentValue and
IfNotEnvironmentValue on the fluent chain, named after the attributes so the two
ways of saying the same thing read alike. Emission was free: the conditions land
on the ServiceModel the existing writer already guards.
Convention and class conditions combine with and. Letting either win would mean
one declaration silently discarding a condition written in the other. Because the
writer emits one guard per ServiceModel, matches are now grouped by implementation
*and* conditions -- merged, two conventions matching one class under different
conditions would have had the stricter guard swallow the other's registrations.
BuildCondition moved to a shared EnvironmentConditionWriter so the service and
decorator writers cannot drift into testing the same attributes differently.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FasPSEiCXbFwobHLur2pnv
Three strands of work that ended up sharing files and are committed together rather than split into commits that would not build on their own. .NET 10 support. Every shipping package multi-targets net8.0 and net10.0 from a single $(LibraryTargetFrameworks) property, so dropping net8.0 when it leaves support in November 2026 is one edit. A .NET 10 project already worked, but the packages brought their Microsoft.Extensions.* 8.x dependency along, and on .NET 10 those live in the shared framework — an older assembly landed in consumer output in place of the one the framework supplies. Each target framework now carries its own baseline version. The generators stay on netstandard2.0, which is what Roslyn requires. global.json pins the 10.0 SDK; CI installs both SDKs, since the 10 SDK builds net8.0 but cannot run its tests. verify-packages.sh builds a consumer per target framework and asserts the dependency groups are framework-matched; it also restores into a private cache, without which a stale extraction of the fixed verify version shadowed the packages under test. xUnit v3 1.0.0 to 3.2.2. The extensibility surface moved across two majors. Module tests now honour the conditional-skip family the way [Fact] and [Theory] do — SkipExceptions on the case, per-row SkipType/SkipUnless/SkipWhen/Label — each of which previously had nowhere to go. Call sites pass arguments by name rather than by position, which is what kept the upgrade to four real errors instead of nineteen misleading ones. DependencyModules.xUnit now references xunit.v3.extensibility.core, the supported package for a library shipping test attributes, replacing three settings that existed only to stop xunit.v3's targets forcing it to be an executable; xunit.analyzers is referenced explicitly because that package does not carry it. Two trait helpers replace equivalents from Xunit.Internal, which carries no compatibility guarantee. A module test also reports where it is declared. The attribute captured no source location and the discoverer forwarded none, so test explorers had nowhere to navigate. Naming two or more modules still cannot capture one — C# does not allow caller-info parameters after a params array — which is pinned by its own test. Mock<T> may be taken directly on a test parameter, and the test-framework interfaces move from DependencyModules.xUnit to DependencyModules.Testing behind ITestMethodContext, so the seam no longer names xUnit types. Breaking: ModuleTestCase gained a skipExceptions parameter in fifth position. Positional callers past the fourth argument need updating; [ModuleTest] users are unaffected. 558 unit, 132 integration and 2 web tests pass on both target frameworks. Coverage 87.7%, package verification green for both. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013me9Eodb4PjJgR4ZNKrC9B
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four strands of work that grew on one branch and share enough files that splitting them now would produce commits that do not build.
Environment conditions become first class (44e8e7c)
[IfEnvironment]and the rest of the family now take effect on[Decorator], so a decorator can exist only where it is wanted. Where the condition does not hold the decorator is never applied, so the service resolves undecorated rather than being wrapped by something that re-tests the environment on every call. Conventions gain the same set asIfEnvironment(…),IfNotEnvironment(…),IfEnvironmentValue(…)and friends, so a whole rule can be gated without repeating the attribute on every class it matches..NET 10 support (bbed38d)
Every shipping package multi-targets
net8.0andnet10.0from a single$(LibraryTargetFrameworks)property, so droppingnet8.0when it leaves support in November 2026 is one edit.A .NET 10 project already worked — a
net8.0assembly loads fine on it — but the packages brought theirMicrosoft.Extensions.*8.x dependency along, and on .NET 10 those live in the shared framework, so an older assembly landed in consumer output in place of the one the framework already supplies. Each target framework now carries its own baseline version.The generators stay on
netstandard2.0, which is what Roslyn requires.global.jsonpins the 10.0 SDK, and CI installs both — the 10 SDK buildsnet8.0but cannot run its tests, because roll-forward stops at the major version boundary.verify-packages.shnow builds a consumer project per target framework and asserts every dependency group is framework-matched. It also restores into a private cache: NuGet keys its global cache on id/version alone, so a stale extraction of the fixed1.0.0-verifyversion had been shadowing the packages under test.xUnit v3 1.0.0 to 3.2.2 (bbed38d)
The extensibility surface moved across two major versions. Module tests now honour the conditional-skip family the way
[Fact]and[Theory]do —SkipExceptionson the test case, and per-rowSkipType/SkipUnless/SkipWhen/Label— each of which previously had nowhere to go.Call sites pass arguments by name rather than by position. That is what kept the upgrade to four real errors rather than nineteen misleading ones: a parameter inserted mid-list silently rebinds everything after it.
DependencyModules.xUnitnow referencesxunit.v3.extensibility.core— the supported package for a library that ships test attributes without being a test project — replacing three settings that existed only to stopxunit.v3's targets forcing it to be an executable.xunit.analyzersis referenced explicitly, because that package does not carry it and the rules would otherwise stop being checked rather than being satisfied.Two trait helpers replace equivalents from
Xunit.Internal, which carries no compatibility guarantee; they were verified behaviour-identical by running both over the same input.A module test also reports where it is declared now. The attribute captured no source location and the discoverer forwarded none, so test explorers had nowhere to navigate to. Naming two or more modules still cannot capture one, because C# does not allow caller-info parameters after a
paramsarray — pinned by its own test so the gap stays visible.Mocking seam (bbed38d)
Mock<T>may be taken directly on a test parameter, and the test-framework interfaces move fromDependencyModules.xUnittoDependencyModules.TestingbehindITestMethodContext, so the seam no longer names xUnit types.Breaking change
ModuleTestCasegained askipExceptionsparameter in fifth position. Positional callers past the fourth argument need updating;[ModuleTest]users are unaffected. This wants a major version.Verification
DependencyModules.TestsSutProject.TestsWebApiApp.TestsConsoleTestProjectverify-packages.shNew trait and source-location assertions were mutation-tested — each fails when the code it covers is deliberately broken.
🤖 Generated with Claude Code
https://claude.ai/code/session_013me9Eodb4PjJgR4ZNKrC9B