Skip to content
fullstackhero

Guide

Running tests

Commands and CI patterns for running the unit / integration / architecture suites locally and in CI.

views 0 Last updated

The kit’s test suites run with the standard dotnet test command. Three layers, three different speed profiles, one set of commands.

Run everything

Terminal window
dotnet test src/FSH.Starter.slnx

This 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

Terminal window
# Unit tests for one module
dotnet test src/Tests/Identity.Tests/
# Architecture tests only — fast, no containers
dotnet test src/Tests/Architecture.Tests/
# Integration tests only — Docker must be running
dotnet test src/Tests/Integration.Tests/
# Production-middleware integration tests (separate assembly, own process)
dotnet test src/Tests/Integration.Middleware.Tests/

Filter by name

Terminal window
# Single test class
dotnet test --filter "FullyQualifiedName~ProductsEndpointTests"
# Single test method
dotnet test --filter "FullyQualifiedName~BrandsEndpointTests.CreateBrand_Should_Return200_And_Persist_When_AuthorizedAdmin"

The ~ operator does substring match; = does exact match. Combine with & and |:

Terminal window
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

Terminal window
# Detailed output — useful for debugging slow tests
dotnet test --logger "console;verbosity=detailed"
# Trx output for CI parsing
dotnet test --logger "trx;LogFileName=test-results.trx"
# Both
dotnet test --logger "console;verbosity=detailed" --logger "trx"

Code coverage

The kit ships coverlet.collector in unit projects. Generate a coverage report:

Terminal window
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-globaltool
reportgenerator -reports:**/coverage.cobertura.xml -targetdir:coveragereport

The 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.

Terminal window
# Verify Docker is reachable before running integration tests
docker info

If 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:

Terminal window
dotnet build src/FSH.Starter.slnx -c Release -warnaserror # the CI build gate
dotnet 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:

Terminal window
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:

Terminal window
# List anything Testcontainers-labelled
docker ps -a --filter "label=org.testcontainers"
# Remove leftovers
docker rm -f $(docker ps -aq --filter "label=org.testcontainers")