Skip to content

Testing

View Markdown

Mandatory for every project.

  • Each acceptance criterion maps to at least one test. Test behaviour through public interfaces, not implementation details.
  • A bug fix starts with a test that fails without the fix.
  • Cover edge cases: empty, boundary and maximum values, invalid input, duplicates, concurrency, time zones and unauthorised access.
  • Test the failure paths you designed: timeouts, retries, fallbacks and error messages.
  • Unit tests for pure logic: fast, numerous, no I/O.
  • Integration tests for boundaries (database, HTTP handlers, queues), using real dependencies such as local services or test containers where practical.
  • End-to-end tests for the key user journeys against the built app. Keep them few, stable and independent of each other.
  • Schema or contract tests for APIs that others consume.
  • Automated accessibility checks (axe or equivalent) on every page template, with zero serious or critical issues.
  • One behaviour per test. The name states the behaviour and the expected result. Use arrange, act, assert.
  • Deterministic: control time, randomness, network and ordering. No fixed sleeps; wait for conditions.
  • Mock only at boundaries you don’t own or that are slow or non-deterministic. Never mock the unit under test or assert on the mock itself.
  • Use synthetic data from factories or fixtures. Never use production personal data.
  • Tests are isolated: they run in any order and in parallel, and clean up what they create.
  • Prefer specific assertions to large snapshots. Review snapshot changes like code.
  • Every pull request runs format, lint, typecheck, unit, integration, build and end-to-end tests. Merging is blocked on failure.
  • Fix a flaky test at once, or quarantine it with a tracked issue. Never re-run until green and move on.
  • Coverage is a signal, not a target. Cover critical logic thoroughly; never write tests just to raise the number.