Testing & TDD
Tests are how we know a change is correct before a person looks at it, and how we keep it correct as the code around it moves. We develop test-first. Not because it is virtuous, but because writing the test first forces you to say what “correct” means before you have talked yourself into whatever the code happens to do.
The loop is small and you run it many times a day.
- Red. Write one failing test for the next small behavior. Run it. Watch it fail for the reason you expect, so you know the test can actually catch the thing.
- Green. Write the least code that makes it pass. Not the elegant version, the passing version.
- Refactor. Now clean it up with the test holding your hand. Remove duplication, fix names, and keep it green.
One behavior at a time. The discipline is writing the test before the code, every time.
What to test
We use Vitest across the monorepo. Aim tests where the logic and the risk are, not at a coverage number.
- Domain first. Value objects, entities, and domain services hold the business rules. Test them hard: valid and invalid inputs, boundaries, and every state transition. These run fast and need no database.
- Application services. The use cases that orchestrate the domain. Cover the happy path and the failure modes.
- Critical paths, end to end. For flows where the pieces have to fit (auth, payments, a full request lifecycle), an integration test against a real Postgres earns its keep. Mock external services.
- Reads, lightly. The query path bypasses the domain and returns plain DTOs. Test the shaping where it is non-trivial, not every projection.
Test behavior through public interfaces, not private internals. A test that breaks every time you rename a method is testing the wrong thing. Do not test the framework.
What CI runs
Every PR into main runs the same checks, scoped to the packages the PR actually changed:
- Migration journal in sync (
db:check) · catches migration SQL and journal drift. - Lint, typecheck, build, test · one
turbo runacross the affected packages, reusing the cache.
Green is the floor for review, not a bonus. Run the same locally before you push: pnpm test, pnpm run lint, pnpm run typecheck, pnpm build.
Fixing a bug
Start with the test, not the fix. Write a test that reproduces the bug and fails. Now you have proof of the bug and a definition of done. Make it pass. The test stays in the suite as a regression guard, so the same bug cannot return quietly later.
Related: Branching & PRs, Code Review, Database Migrations, Build