Skip to content

chore(tests): move the test projects to xUnit v3 - #1467

Merged
iammukeshm merged 1 commit into
mainfrom
chore/xunit-v3
Nov 4, 2026
Merged

iammukeshm merged 1 commit into
mainfrom
chore/xunit-v3

Conversation

@iammukeshm

Copy link
Copy Markdown
Member

Closes #1457.

All 16 test projects move from xunit 2.9.3 to xUnit v3 (xunit.v3.mtp-off 4.0.2).

Why mtp-off. The default xunit.v3 package turns on Microsoft.Testing.Platform, and then the .NET 10 SDK refuses VSTest-style dotnet test unless global.json opts into the new runner. The template doesn't ship global.json, so every scaffolded project would lose dotnet test. mtp-off keeps VSTest: dotnet test, --filter, the coverlet collector and coverage.runsettings all work as before, and CI's test steps don't change. Moving to MTP can be a separate change.

The migration itself:

  • Test projects are executables now (<OutputType>Exe</OutputType>).
  • IAsyncLifetime returns ValueTask in v3. FshWebApplicationFactory and MiddlewareWebApplicationFactory now override WebApplicationFactory.DisposeAsync() instead of hiding it with new, so there's a single disposal path. The five test classes with their own lifetimes are updated too.
  • xUnit1051 is off for src/Tests (src/.editorconfig, with the reason). I tried its code fix first. It also adds TestContext.Current.CancellationToken to NSubstitute setups and Received() checks, which changes what the mock matches, because handlers under test get CancellationToken.None. In the integration tests it changes about 1,900 lines and adds that token to almost every one. The suite has no test timeouts for the token to serve.

Hidden test-order dependencies. v3 orders tests differently, and that exposed four places where tests relied on running in a particular order. The first full run had 189 failures. All four fixes are in the test harness; no production code changes.

  1. WithWebHostBuilder hosts (webhook tests) replace two process-wide statics, Hangfire's JobStorage.Current and the static Audit publisher, and leave them pointing at their own storage and channel after disposal. Later enqueues threw ObjectDisposedException (most of the 189), and request audits went into a channel nobody reads. The new PrimaryHostStaticsRestorer points both back at the shared host whenever a host stops.
  2. TenantProvisioningFailureTests and RetryTenantProvisioningTests left tenants with an unreachable database in the store, so the legacy public-key migration job (which walks every tenant) failed in StorageVisibilityRootTests. They now remove their tenants through a new FshWebApplicationFactory.RemoveTenantsAsync.
  3. The audit tenant-isolation poll read only the newest 100 rows, but a new tenant can have more than 100 EntityChange seed rows, depending on what earlier tests created. It now excludes EntityChange.
  4. The "within the auth limit" rate-limit test shared TestServer's null-IP partition with the test that exhausts it. It now uses its own client IP.

Tested:

  • dotnet build src/FSH.Starter.slnx: 0 warnings, 0 errors.
  • dotnet test src/FSH.Starter.slnx: 2,225/2,225 passing. Every project's count matches main's last Backend CI run: Integration 805, Middleware 7, Framework 373, Identity 352, and so on.
  • --collect "XPlat Code Coverage" --settings coverage.runsettings still produces coverage.cobertura.xml.

Docs: fullstackhero/docs PR to follow (testing pages and changelog).

All 16 test projects move from xunit 2.9.3 to xunit.v3.mtp-off 4.0.2 and
become executables. mtp-off keeps VSTest, so dotnet test, the coverlet
collector and coverage.runsettings work unchanged, and scaffolded projects
need no global.json test-runner setting.

IAsyncLifetime returns ValueTask in v3; both WebApplicationFactory fixtures
now override DisposeAsync instead of hiding it. xUnit1051 is off for
src/Tests: its code fix makes NSubstitute setups and Received() checks
expect the test's cancellation token.

v3's different test order exposed four hidden dependencies, fixed in the
harness:
- Derived WithWebHostBuilder hosts left JobStorage.Current and the static
  Audit publisher pointing at their disposed storage and channel.
  PrimaryHostStaticsRestorer repoints both at the shared host on stop.
- Provisioning-failure tests left unreachable-database tenants behind;
  they now remove them via FshWebApplicationFactory.RemoveTenantsAsync.
- The audit tenant-isolation poll now excludes EntityChange seed rows,
  which can fill more than the first page.
- The within-limit rate-limit test uses its own client IP instead of the
  partition the 429 test exhausts.
@iammukeshm
iammukeshm merged commit 8f211c2 into main Nov 4, 2026
17 checks passed
@iammukeshm
iammukeshm deleted the chore/xunit-v3 branch November 4, 2026 11:35
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.

Move the test projects from xUnit v2 to xUnit v3

1 participant