--- name: dotnet-add-testing description: >- Adds test infrastructure to a .NET project. Scaffolds xUnit project, coverlet, layout. metadata: short-description: .NET skill guidance for testing tasks --- # dotnet-add-testing Add test infrastructure scaffolding to an existing .NET project. Creates test projects with xUnit, configures code coverage with coverlet, and sets up the conventional directory structure. ## Scope - Test project creation with xUnit and coverlet - Conventional directory structure (tests/ mirroring src/) - Project reference wiring and test SDK configuration ## Out of scope - In-depth testing patterns (xUnit v3, WebApplicationFactory, UI testing) -- see [skill:dotnet-testing-strategy] **Prerequisites:** Run [skill:dotnet-version-detection] first to determine SDK version and TFM. Run [skill:dotnet-project-analysis] to understand existing solution structure. Cross-references: [skill:dotnet-project-structure] for overall solution layout conventions, [skill:dotnet-scaffold-project] which includes test scaffolding in new projects, [skill:dotnet-add-analyzers] for test-specific analyzer suppressions. --- ## Test Project Structure Follow the convention of mirroring `src/` project names under `tests/`: ````text MyApp/ ├── src/ │ ├── MyApp.Core/ │ ├── MyApp.Api/ │ └── MyApp.Infrastructure/ └── tests/ ├── MyApp.Core.UnitTests/ ├── MyApp.Api.UnitTests/ ├── MyApp.Api.IntegrationTests/ └── Directory.Build.props # Test-specific build settings ```xml Naming conventions: - `*.UnitTests` -- isolated tests with no external dependencies - `*.IntegrationTests` -- tests that use real infrastructure (database, HTTP, file system) - `*.FunctionalTests` -- end-to-end tests through the full application stack --- ## Step 1: Create the Test Project ```bash # Create xUnit test project dotnet new xunit -n MyApp.Core.UnitTests -o tests/MyApp.Core.UnitTests # Add to solution dotnet sln add tests/MyApp.Core.UnitTests/MyApp.Core.UnitTests.csproj # Add reference to the project under test dotnet add tests/MyApp.Core.UnitTests/MyApp.Core.UnitTests.csproj \ reference src/MyApp.Core/MyApp.Core.csproj ```csharp ### Clean Up Generated Project Remove properties already defined in `Directory.Build.props`: ```xml ```csharp With CPM, `Version` attributes are managed in `Directory.Packages.props`. Remove them from the generated `.csproj`. --- ## Step 2: Add Test-Specific Build Properties Create `tests/Directory.Build.props` to customize settings for all test projects: ```xml false true true false ```text This imports the root `Directory.Build.props` (for shared settings like `Nullable`, `ImplicitUsings`, `LangVersion`) and overrides test-specific properties. --- ## Step 3: Register Test Packages in CPM Add test package versions to `Directory.Packages.props`: ```xml ```text ### Optional: Mocking Library Add a mocking library if the project needs test doubles: ```xml ```xml Or for assertion libraries: ```xml ```xml --- ## Step 4: Configure Code Coverage ### Coverlet (Collector Mode) The `coverlet.collector` package integrates with `dotnet test` via the data collector. No additional configuration is needed for basic coverage. Generate coverage reports: ```bash # Collect coverage (Cobertura format by default) dotnet test --collect:"XPlat Code Coverage" # Results appear in TestResults/*/coverage.cobertura.xml ```xml ### Coverage Thresholds For CI enforcement, use `coverlet.msbuild` for threshold checks: ```xml ```xml ```bash # Enforce minimum coverage threshold dotnet test /p:CollectCoverage=true \ /p:CoverageOutputFormat=cobertura \ /p:Threshold=80 \ /p:ThresholdType=line ```text ### Coverage Report Generation Use `reportgenerator` for human-readable HTML reports: ```bash # Install globally dotnet tool install -g dotnet-reportgenerator-globaltool # Generate HTML report reportgenerator \ -reports:"tests/**/coverage.cobertura.xml" \ -targetdir:coverage-report \ -reporttypes:Html ```xml --- ## Step 5: Add EditorConfig Overrides for Tests In the root `.editorconfig`, add test-specific relaxations: ```ini [tests/**.cs] # Allow underscores in test method names (Given_When_Then or Should_Behavior) dotnet_diagnostic.CA1707.severity = none # Test parameters are validated by the framework dotnet_diagnostic.CA1062.severity = none # ConfigureAwait not relevant in test context dotnet_diagnostic.CA2007.severity = none # Tests often have intentionally unused variables for assertions dotnet_diagnostic.IDE0059.severity = suggestion ```text --- ## Step 6: Write a Starter Test Replace the template-generated `UnitTest1.cs` with a properly structured test: ```csharp namespace MyApp.Core.UnitTests; public class SampleServiceTests { [Fact] public void Method_Condition_ExpectedResult() { // Arrange var sut = new SampleService(); // Act var result = sut.DoWork(); // Assert Assert.NotNull(result); } [Theory] [InlineData(1, 2, 3)] [InlineData(0, 0, 0)] [InlineData(-1, 1, 0)] public void Add_TwoNumbers_ReturnsSum(int a, int b, int expected) { var result = Calculator.Add(a, b); Assert.Equal(expected, result); } } ```text ### Test Naming Convention Use the pattern `Method_Condition_ExpectedResult`: - `CreateUser_WithValidInput_ReturnsUser` - `GetById_WhenNotFound_ReturnsNull` - `Delete_WithoutPermission_ThrowsUnauthorized` --- ## Verify After adding test infrastructure, verify everything works: ```bash # Restore (regenerate lock files if using CPM) dotnet restore # Build (verifies project references and analyzer config) dotnet build --no-restore # Run tests dotnet test --no-build # Run with coverage dotnet test --collect:"XPlat Code Coverage" ```text --- ## Adding Integration Test Projects For integration tests that need `WebApplicationFactory` or database access: ```bash dotnet new xunit -n MyApp.Api.IntegrationTests -o tests/MyApp.Api.IntegrationTests dotnet sln add tests/MyApp.Api.IntegrationTests/MyApp.Api.IntegrationTests.csproj dotnet add tests/MyApp.Api.IntegrationTests/MyApp.Api.IntegrationTests.csproj \ reference src/MyApp.Api/MyApp.Api.csproj ```csharp Add integration test packages to CPM (match the `Microsoft.AspNetCore.Mvc.Testing` major version to the target framework -- e.g., `8.x` for `net8.0`, `9.x` for `net9.0`, `10.x` for `net10.0`): ```xml ```xml Integration test depth (WebApplicationFactory patterns, test containers, database fixtures) -- see [skill:dotnet-integration-testing]. --- ## What's Next This skill covers test project scaffolding. For deeper testing guidance: - **xUnit v3 features and patterns** -- [skill:dotnet-xunit] - **Integration testing with WebApplicationFactory** -- [skill:dotnet-integration-testing] - **UI testing (Blazor, MAUI, Uno)** -- [skill:dotnet-blazor-testing], [skill:dotnet-maui-testing], [skill:dotnet-uno-testing] - **Snapshot testing** -- [skill:dotnet-snapshot-testing] - **Test quality and coverage enforcement** -- [skill:dotnet-test-quality] - **CI test reporting** -- [skill:dotnet-add-ci] for starter, [skill:dotnet-gha-build-test] and [skill:dotnet-ado-build-test] for advanced --- ## References - [xUnit Documentation](https://xunit.net/) - [Coverlet](https://github.com/coverlet-coverage/coverlet) - [.NET Testing Best Practices](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices) - [Microsoft.NET.Test.Sdk](https://www.nuget.org/packages/Microsoft.NET.Test.Sdk) - [ReportGenerator](https://github.com/danielpalme/ReportGenerator) ````