--- name: tunit description: "Write, run, or repair .NET tests that use TUnit. Use when a repo uses `TUnit`, `TUnit.Playwright`, `[Test]`, `[Arguments]`, `ClassDataSource`, `SharedType.PerTestSession`, or Microsoft.Testing.Platform-based execution. Preserve TUnit's parallel-by-default model and constrain only destructive shared-state collisions. DO NOT USE FOR: xUnit projects; MSTest projects." --- # TUnit ## Trigger On - the repo uses TUnit - you need to add, run, debug, or repair TUnit tests - the repo uses Microsoft.Testing.Platform-based test execution - the repo uses `ClassDataSource<...>(Shared = SharedType.PerTestSession)`, `TUnit.Playwright`, or `--treenode-filter` ## Do Not Use For - xUnit projects - MSTest projects - generic test strategy with no TUnit-specific mechanics ## Inputs - the nearest `AGENTS.md` - the test project file and package references - the repo's current TUnit execution command ## Workflow 1. Confirm the project really uses TUnit and not a different MTP-based framework. 2. Read the repo's real `test` command from `AGENTS.md`. If the repo has no explicit command yet, start with `dotnet test PROJECT_OR_SOLUTION`. 3. Keep the TUnit execution model intact: - tests are source-generated at build time - tests run in parallel by default - on .NET 10, test modules also run in parallel by default up to `Environment.ProcessorCount` - built-in analyzers should remain enabled 4. Choose the fixture level deliberately: - plain TUnit tests for isolated logic - shared AppHost/Aspire fixtures for HTTP, SignalR, SSE, or UI flows - `WebApplicationFactory` layered over shared Aspire infra when tests need Host DI services, `IGrainFactory`, or other runtime internals 5. Reuse expensive fixtures with `ClassDataSource(Shared = SharedType.PerTestSession)` instead of booting distributed infrastructure per test. Fixture reuse does not serialize consumers: keep the fixture concurrency-safe and give each test unique mutable state. 6. Keep tests and test modules parallel. Do not add `--max-parallel-test-modules 1`, `TUNIT_MAX_PARALLEL_TESTS=1`, `[assembly: NotInParallel]`, a class-wide `[NotInParallel]`, or an equivalent global restriction. 7. Use keyed `[NotInParallel("collision-domain")]` only on the smallest tests that perform destructive changes to the same shared state and can corrupt one another. A shared read-only fixture, expensive startup, module boundary, or vague CI-stability concern is not a reason to limit parallelism. 8. Run the narrowest useful scope first with `dotnet test ... --treenode-filter "..."` on .NET 10. Use the older `--` separator only when the repository is pinned to an SDK that requires it. 9. Follow `Test Output Budget`: show bounded root-error evidence and link coverage, Playwright screenshots, and HTML artifacts; never dump host logs. 10. Use `[Test]`, `[Arguments]`, hooks, and dependencies only when they make the scenario clearer, not because the framework allows it. ## Test Output Budget - Keep native runner progress and ANSI enabled (`--progress on --ansi on` for supported MTP/TUnit runners); use a PTY locally. Do not replay progress redraws into model context. Use the detected runner's flags, not MTP switches on VSTest. - Show warnings and errors plus one final summary (counts, duration, exit code). Configure test-owned console logging at `Warning`; keep Information/Debug/Trace, successful-test output, and expected negative-test noise out of context. Quiet build verbosity alone does not filter application logs. - On failure, crash, startup error, or timeout, show the failing test/resource, root exception, and relevant stack frames. Deduplicate; cap each diagnostic tool response at 80 lines / 8 KiB, whichever comes first. Never automatically dump stdout/stderr, host logs, browser console history, DOM/HTML, TRX, or crash artifacts. - Keep necessary diagnostics in size-bounded or rotating artifacts and link them. Search by exact failure/correlation; read bounded excerpts, never whole logs. Capture/filter noisy output before tool delivery, preserve the real exit code, and disclose truncation. Monitor actual activity; silence alone does not prove a hang. ## Bootstrap When Missing If `TUnit` is requested but not configured yet: 1. Detect current state: - `rg -n "TUnit|Microsoft\\.Testing\\.Platform" -g '*.csproj' -g 'Directory.Build.*' .` 2. Add the minimal package set to the test project: - `dotnet add TEST_PROJECT.csproj package TUnit` - do not add `Microsoft.NET.Test.Sdk` to a current TUnit project; it selects the VSTest path and conflicts with the normal Microsoft.Testing.Platform setup 3. Keep the runner model explicit in `AGENTS.md` and CI: - record that the repo uses Microsoft.Testing.Platform-compatible execution for this test project - record the exact `dotnet test TEST_PROJECT.csproj` command the repo will use 4. Add one small executable test using `[Test]`. 5. Run `dotnet test TEST_PROJECT.csproj` and return `status: configured` or `status: improved`. 6. If the repo intentionally standardizes on xUnit or MSTest, return `status: not_applicable` unless migration is explicitly requested. ## Deliver - TUnit tests that respect source generation and parallel execution - commands that work in local and CI runs - framework-specific verification guidance for the repo - a fixture strategy that matches the actual test scope: logic-only, AppHost/API, Host DI/grains, or Playwright UI ## Validate - the command matches the repo's TUnit runner style - focused runs use `--treenode-filter` rather than VSTest-style `--filter` - .NET 10 runs pass MTP options directly without a `--` separator - no global or assembly-wide single-thread setting has been introduced - any keyed non-parallel group is limited to tests with a named destructive shared-state collision - shared distributed fixtures use `SharedType.PerTestSession` or an equivalent reuse pattern - fixture and shared infrastructure are safe for concurrent consumers; mutable data is isolated per test - built-in TUnit analyzers remain active - coverage tooling matches Microsoft.Testing.Platform if coverage is enabled - UI failures link artifacts and server-side failures expose bounded root-error excerpts to avoid blind reruns ## Test Harness ```mermaid flowchart LR A["TUnit task"] --> B{"What does the test need?"} B -->|"Single component only"| C["Plain TUnit test"] B -->|"HTTP / SignalR / resource graph"| D["Shared Aspire/AppHost fixture"] B -->|"Host DI / grains / runtime services"| E["Shared Aspire/AppHost fixture + WebApplicationFactory"] B -->|"Browser automation"| F["Shared Aspire/AppHost fixture + Playwright"] C & D & E & F --> G["Run focused with --treenode-filter"] G --> H["Show bounded errors and link artifacts"] ``` ## Load References - [references/patterns.md](references/patterns.md) - [references/migration.md](references/migration.md) - [references/tunit.md](references/tunit.md) - [references/integration-testing.md](references/integration-testing.md) ## Running Tests TUnit uses Microsoft.Testing.Platform. Use `--treenode-filter` for filtering, not VSTest `--filter`. On .NET 10, pass MTP switches directly; older SDKs may require `--`. ```bash # Run all tests dotnet test --solution MySolution.sln # Run one test project dotnet test --project tests/MyProject.Tests/MyProject.Tests.csproj # Filter by class dotnet test --project tests/MyProject.Tests/MyProject.Tests.csproj --treenode-filter "/*/*/CalculatorTests/*" # Filter by category dotnet test --project tests/MyProject.Tests/MyProject.Tests.csproj --treenode-filter "/*/*/*/*[Category=Integration]" # Coverage on Microsoft.Testing.Platform dotnet test --solution MySolution.sln --coverage --coverage-output coverage.cobertura.xml --coverage-output-format cobertura # Raw runner help when the repo needs direct TUnit app switches dotnet run --project tests/MyProject.Tests/MyProject.Tests.csproj -- --help ``` Filter syntax: `////` with `*` wildcards. See [references/patterns.md](references/patterns.md) for full examples. ## Example Requests - "Run this TUnit project correctly." - "Fix our TUnit CI command." - "Add a regression test in TUnit without breaking parallelism."