The test suites

The Starter is tested at four levels, plus a visual review that is the only thing looking at layout. Each level answers a question the others cannot.

LevelProjectsAnswers
UnitBlouppy.<Domain>.Models.UnitTests, Blouppy.<Domain>.UnitTests, Blouppy.ApiQuery.UnitTests, Blouppy.Web.UnitTests, Blouppy.Infrastructure.<Concern>.UnitTestsDoes this rule, service or mapping do what it says, with its ports mocked?
IntegrationBlouppy.Infrastructure.<Concern>.IntegrationTests, Blouppy.Web.IntegrationTestsDoes the adapter work against the real thing — PostgreSQL, a disk, an S3 store? Does the API answer, with the right rights?
ArchitectureBlouppy.Architecture.UnitTestsDoes every reference still point inward? Is every write audited or excused?
End to ende2e/Blouppy.E2E.ApiDoes the whole graph work together — the gateway's session, the identity host's tokens, the bus?
Visualnpm run gallery in each front-end appDoes every page still look right, in both themes, at three widths?

Running them

dotnet build back/            # warnings are errors
dotnet test back/             # unit, integration and architecture tests

cd back                       # global.json selects Microsoft.Testing.Platform
dotnet test --project Blouppy.Workspaces.UnitTests/Blouppy.Workspaces.UnitTests.csproj \
  --filter-class "*WorkspacesServiceTests"     # one class while iterating
cd front/application          # or landing, docs, playground
npm test                      # Vitest, in jsdom
npm run lint && npm run format-check

The integration tests need Docker: each project that touches a real dependency starts its own container with Testcontainers.

Conventions

Stack. xUnit v3, AutoFixture, Moq and FluentAssertions, with global usings in each test project.

Naming.Should_[action]_when_[condition] — Should_return_Workspace_when_it_exists.

Shape. Arrange / Act / Assert, each marked by a comment; always pass TestContext.Current.CancellationToken.

Comparing objects..BeEquivalentTo() against a typed expected object, never an anonymous one: a property added to the type later would go unchecked, and the test would stay green. Exclude only what legitimately differs.

Data. Each test builds what it asserts against — Given_a_*() helpers in the DataSeed classes, Setup*Mock() for mocks. Tests never rely on the Development sample data.

Database tests use three separate DbContext instances — one to arrange, one to act, one to assert — so nothing passes because the change tracker still holds an entity that was never saved. See the choice.

Front-end specs that render a translated template import provideTranslocoTesting(), which loads the real en.json and fr.json: a missing key fails the test instead of rendering as the key.

What the integration tests do not see

Blouppy.Web.IntegrationTests hosts the API alone, with WebApplicationFactory: it mocks ICurrentUserService and runs the bus on in-process queues. Nothing there reaches the gateway, its cookie session or a real identity host. That is what the end-to-end suite is for.

Continuous integration

.github/workflows/build.yml builds and tests every pull request by changed path: a change under front/docs/ runs the documentation site's job, a change under back/ the .NET one. Each job runs that project's checks — lint, format, tests, and the build where it applies — and the .NET job also checks that the EF Core model snapshots match the model, so a forgotten migration fails CI.

The end-to-end suite runs on a version tag, on a pull request labelled e2e 🧪, and by hand from the Actions tab. Create that label in your repository for it to be usable. renovate.json keeps dependencies current once the Renovate GitHub App is installed.