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: sub-second each
  • Architecture project: ~1-2 seconds
  • Integration project: 30-60 seconds (cold Testcontainers) / ~5-10 seconds (warm)

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/

Filter by name

Terminal window
# Single test class
dotnet test --filter "FullyQualifiedName~ProductsEndpointTests"
# Single test method
dotnet test --filter "FullyQualifiedName~ProductsEndpointTests.POST_products_Should_ReturnCreated_And_PersistProduct_When_PayloadIsValid"
# By trait or category
dotnet test --filter "Category=Integration"

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 class-level parallelism with shared fixturesIClassFixture<FshWebApplicationFactory> shares the factory across tests in a class; xUnit runs classes in parallel where the fixtures permit.

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. Coverage targets in the kit hover around 75-85% for unit projects; integration tests push effective coverage higher.

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

A minimal workflow:

.github/workflows/test.yml
name: tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Restore
run: dotnet restore src/FSH.Starter.slnx
- name: Build
run: dotnet build src/FSH.Starter.slnx --no-restore -c Release
- name: Test (unit + architecture)
run: dotnet test src/FSH.Starter.slnx --no-build -c Release
--filter "FullyQualifiedName!~Integration.Tests"
--logger "trx;LogFileName=test-results.trx"
- name: Test (integration)
run: dotnet test src/Tests/Integration.Tests/ --no-build -c Release
--logger "trx;LogFileName=integration-results.trx"
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: '**/*.trx'

GitHub-hosted runners (ubuntu-latest) have Docker pre-installed, so Testcontainers works without extra setup.

CI — speed optimisations

For repos that run tests on every push, the slow piece is integration tests. Three knobs:

  • Skip integration on draft PRs. Run unit + architecture only on pull_request: types: [opened, synchronize] and integration on push: branches: [main].
  • Cache the NuGet packages. actions/cache against ~/.nuget/packages keyed by **/packages.lock.json.
  • Reuse the Testcontainers image cache. Cache /var/lib/docker (advanced) or accept the first-run cold start (simpler).

For repos with many integration tests, shard them across multiple jobs:

strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: dotnet test src/Tests/Integration.Tests/ --filter "FullyQualifiedName~Shard${{ matrix.shard }}"

Tag tests with [Trait("Shard", "1")] to assign them.

Watch mode (local iteration)

dotnet watch reruns tests on file change — useful while iterating on a single test:

Terminal window
dotnet watch test --project src/Tests/Identity.Tests/ \
-- --filter "FullyQualifiedName~RegisterUserCommandHandlerTests"

It picks up file edits via dotnet-watch, recompiles, and reruns the filter. Fast feedback loop without the project’s startup cost on every change.

Container cleanup

Testcontainers spins up containers and keeps them running between test sessions in the same process. If something goes wrong (a test killed by Ctrl-C mid-run), containers can leak:

Terminal window
# List leaked Testcontainers containers
docker ps -a | grep testcontainers
# Stop them
docker stop $(docker ps -aq --filter "label=testcontainers")
# Remove them
docker rm $(docker ps -aq --filter "label=testcontainers")
# Or just everything Testcontainers-labelled
docker container prune --filter "label=testcontainers"