Test-first — Claude Code rules
Philosophy
- Write the test before the change when fixing a regression. Reproduce the bug, watch it fail, then make it pass.
- For new features, write the smallest end-to-end test that demonstrates the feature works. Iterate inward from there.
Layering
- Unit tests cover pure functions / single classes. No I/O, no clock, no network. Fast (<10ms).
- Integration tests cover the seam between code we own and code we don't (DB, queue, HTTP).
- End-to-end tests cover real user flows. Few of them, but they run against the real stack.
Test names
- Describe behaviour, not implementation.
it("rejects expired tokens")✅ notit("calls verifyJwt")❌. - One assertion per behaviour. Multiple assertions are OK only when they describe the same behaviour.
What not to mock
- Don't mock code we own. If a function is hard to test without mocking, restructure it.
- Don't mock the database for integration tests — use a real test database (docker compose, transactional rollback per test).
Snapshots
- Snapshots are for stable, semantic outputs (rendered components, SQL). Don't snapshot deep JSON with timestamps / IDs.
- Review snapshot diffs in PR — never blindly accept.
Coverage
- Coverage is a smell detector, not a goal. 100% coverage with bad assertions ships bugs.