The kit’s test suites run with the standard dotnet test command. Three layers, three different speed profiles, one set of commands.
Run everything
dotnet test src/FSH.Starter.slnxThis runs every test project. Expect:
- Unit projects: seconds each
- Architecture project: a few seconds — no containers
- Integration projects: a few minutes — container startup, migrations, and ~700 real HTTP round-trips (Docker required)
Run a single project
# Unit tests for one moduledotnet test src/Tests/Identity.Tests/
# Architecture tests only — fast, no containersdotnet test src/Tests/Architecture.Tests/
# Integration tests only — Docker must be runningdotnet test src/Tests/Integration.Tests/
# Production-middleware integration tests (separate assembly, own process)dotnet test src/Tests/Integration.Middleware.Tests/Filter by name
# Single test classdotnet test --filter "FullyQualifiedName~ProductsEndpointTests"
# Single test methoddotnet test --filter "FullyQualifiedName~BrandsEndpointTests.CreateBrand_Should_Return200_And_Persist_When_AuthorizedAdmin"The ~ operator does substring match; = does exact match. Combine with & and |:
dotnet test --filter "FullyQualifiedName~Catalog & FullyQualifiedName~Product"Parallelism
xUnit runs test classes in parallel within a single project by default. Tests within a class run sequentially unless marked otherwise.
To disable parallelism across classes (e.g. for shared state):
# xunit.runner.json in the test project root{ "parallelizeTestCollections": false}The kit’s integration project uses a collection fixture — every test class joins [Collection(FshCollectionDefinition.Name)], sharing one FshWebApplicationFactory (and one container set) across the whole suite. xUnit runs classes within a collection sequentially, which is why unique test data matters less than raw isolation here — but keep names unique anyway; it costs nothing.
Verbose output
# Detailed output — useful for debugging slow testsdotnet test --logger "console;verbosity=detailed"
# Trx output for CI parsingdotnet test --logger "trx;LogFileName=test-results.trx"
# Bothdotnet test --logger "console;verbosity=detailed" --logger "trx"Code coverage
The kit ships coverlet.collector in unit projects. Generate a coverage report:
dotnet test src/Tests/Identity.Tests/ --collect:"XPlat Code Coverage"
# Generates a .cobertura.xml file under TestResults/# Convert to HTML with the reportgenerator tool:dotnet tool install --global dotnet-reportgenerator-globaltoolreportgenerator -reports:**/coverage.cobertura.xml -targetdir:coveragereportThe HTML report lands in coveragereport/index.html. CI collects coverage from the unit and integration jobs separately, merges the two with ReportGenerator, and fails the build below 80% line coverage — a ratchet that gets raised as coverage grows. See CI/CD.
Docker requirement for integration tests
Testcontainers needs a running Docker daemon. Locally, Docker Desktop on macOS / Windows, Docker Engine on Linux. In CI, the runner image must have Docker available — GitHub-hosted runners do; self-hosted runners need explicit setup.
# Verify Docker is reachable before running integration testsdocker infoIf Docker isn’t available, integration tests fail with Testcontainers.DockerNotAvailableException.
CI — GitHub Actions
The kit ships its own path-scoped workflows in .github/workflows/ — you don’t need to write one. On every backend change, backend.yml builds with -warnaserror, runs the 12 unit + architecture projects and the two integration projects (each with coverage collection), smoke-tests the DbMigrator container, and merges coverage behind an 80% line-coverage gate. GitHub-hosted runners (ubuntu-latest) have Docker pre-installed, so Testcontainers works without extra setup.
The full pipeline — jobs, gates, publishing — is documented on the CI/CD page. Local parity:
dotnet build src/FSH.Starter.slnx -c Release -warnaserror # the CI build gatedotnet test src/FSH.Starter.slnx -c Release # everything (needs Docker)Watch mode (local iteration)
dotnet watch reruns tests on file change — useful while iterating on a single test:
dotnet watch --project src/Tests/Identity.Tests/ test \ --filter "FullyQualifiedName~RegisterUserCommandHandlerTests"It picks up file edits, recompiles, and reruns the filter. Fast feedback loop without retyping the command on every change.
Container cleanup
The kit’s containers are created with WithAutoRemove(true), and Testcontainers’ reaper (Ryuk) cleans up after crashed runs — so leaks are rare. If a hard kill (machine sleep, Docker restart mid-run) leaves something behind:
# List anything Testcontainers-labelleddocker ps -a --filter "label=org.testcontainers"
# Remove leftoversdocker rm -f $(docker ps -aq --filter "label=org.testcontainers")Related
- Unit tests — what runs fastest.
- Integration tests — what runs slowest.
- Writing new tests — recipes for each layer.
- Fixtures & seed data — what the integration tests share.