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.
| Level | Projects | Answers |
|---|---|---|
| Unit | Blouppy.<Domain>.Models.UnitTests, Blouppy.<Domain>.UnitTests, Blouppy.ApiQuery.UnitTests, Blouppy.Web.UnitTests, Blouppy.Infrastructure.<Concern>.UnitTests | Does this rule, service or mapping do what it says, with its ports mocked? |
| Integration | Blouppy.Infrastructure.<Concern>.IntegrationTests, Blouppy.Web.IntegrationTests | Does the adapter work against the real thing — PostgreSQL, a disk, an S3 store? Does the API answer, with the right rights? |
| Architecture | Blouppy.Architecture.UnitTests | Does every reference still point inward? Is every write audited or excused? |
| End to end | e2e/Blouppy.E2E.Api | Does the whole graph work together — the gateway's session, the identity host's tokens, the bus? |
| Visual | npm run gallery in each front-end app | Does 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.