| Project | Kind | Needs Docker? | Notes |
|---|---|---|---|
tests/CloudEngAgent.Domain.Tests |
Unit | No | Pure domain types; fast, deterministic. |
tests/CloudEngAgent.Api.Tests |
Unit / WAF | No | Uses WebApplicationFactory with stub adapters. |
tests/CloudEngAgent.Mcp.Server.Tests |
Unit | No | MCP tool argument validation, SQL guards. |
tests/CloudEngAgent.Infrastructure.Tests |
Integration | Yes | Spins up SQL Server via Testcontainers. |
The CI pipeline runs the unit projects and the integration project in separate jobs. To mirror it locally:
dotnet build --configuration Release -warnaserror
dotnet test tests/CloudEngAgent.Domain.Tests --configuration Release --no-build
dotnet test tests/CloudEngAgent.Api.Tests --configuration Release --no-build
dotnet test tests/CloudEngAgent.Mcp.Server.Tests --configuration Release --no-build
dotnet test tests/CloudEngAgent.Infrastructure.Tests --configuration Release --no-build--no-build reuses the assemblies you just built, which is faster and
guarantees you're testing exactly what compiled.
dotnet test tests/CloudEngAgent.Api.Tests --filter "FullyQualifiedName~Runs"
dotnet test tests/CloudEngAgent.Domain.Tests --filter "FullyQualifiedName=CloudEngAgent.Domain.Tests.RunTests.Cancel_marks_run_as_cancelled"The filter syntax is the standard
dotnet test --filter
expression.
CI collects coverage with --collect:"XPlat Code Coverage" and uploads the
results as the unit-test-results artifact. To do the same locally:
dotnet test tests/CloudEngAgent.Api.Tests \
--configuration Release --no-build \
--collect:"XPlat Code Coverage" \
--results-directory ./TestResultsA coverage.cobertura.xml is written under TestResults/<guid>/. Use
reportgenerator (or your IDE's coverage view) to render it.
CloudEngAgent.Infrastructure.Tests uses Testcontainers to spin up a real
SQL Server 2022 container per test collection. The fixture
(MsSqlContainerFixture.cs)
detects whether Docker is available:
- ✅ Docker available → tests run against the container.
- 🟡 Docker missing → tests are skipped with a clear reason, not failed.
This means you can develop and run the unit suite without Docker, and let CI
exercise the integration path. If you're touching Persistence/ code,
always run the integration tests locally before pushing.
First-time runs are slow because Docker has to pull the SQL Server image (several GB). Subsequent runs reuse the cached image.
- Match the existing layout: one test class per production class, nested under a folder that mirrors the source folder.
- Name tests with the
Method_state_expectationconvention (e.g.Cancel_when_already_finished_throws). - Prefer arrange/act/assert with a blank line between sections. No comments needed if the structure is clear.
- For API tests, use the existing
WebApplicationFactory-based base class (look intests/CloudEngAgent.Api.Tests/for the pattern). It pre-wires the stub adapters so tests are hermetic. - For integration tests, depend on the
MsSqlxUnit collection so the shared container fixture is reused:Honor[Collection("MsSql")] public sealed class MyNewIntegrationTests(MsSqlContainerFixture fixture) { ... }
fixture.SkipReasonif it's set so your test skips cleanly when Docker is unavailable.
xUnit1031warnings: don't.Result/.Wait()async tasks in tests — make the test methodasync Taskandawait.-warnaserrordoesn't apply to test projects, but warnings still show up in the build log. Treat them as bugs anyway; they're often pointing at a real issue in the test code.- Hanging tests: usually a missing
awaitor a deadlockedIAsyncLifetime. Run with--blameto capture a hang dump:dotnet test tests/CloudEngAgent.Api.Tests --blame --blame-hang-timeout 60s - Flaky integration tests: try a clean container by deleting any
cloudeng-mssql/Testcontainers leftovers (docker ps -a, thendocker rm <id>). The fixture creates fresh containers per run, but a stuck previous run can hold the port.
Before opening a PR, run:
dotnet build --configuration Release -warnaserror
dotnet test tests/CloudEngAgent.Domain.Tests --configuration Release --no-build
dotnet test tests/CloudEngAgent.Api.Tests --configuration Release --no-build
dotnet test tests/CloudEngAgent.Mcp.Server.Tests --configuration Release --no-build
dotnet test tests/CloudEngAgent.Infrastructure.Tests --configuration Release --no-build # if Docker is availableIf all four pass locally, CI will almost certainly pass too.