Testing Standards
Testing Strategy — Prioritize by ROI
- Write mostly integration tests — they provide the highest confidence-to-cost ratio (testing trophy model)
- Use unit tests for pure logic with complex branching — not for trivial code
- Use end-to-end tests sparingly for critical user journeys — they are slow and brittle
- Use static analysis (types, linters) as the first line of defense — catches bugs before tests run
Coverage & Requirements
- Maintain minimum 80% code coverage
- All new features require tests before merging — untested code is a liability
- All bug fixes require a regression test — prevents the same bug from recurring
- Critical paths require integration tests
Test-Driven Development (TDD)
- RED: Write a failing test that describes the expected behavior
- GREEN: Write the minimal implementation to make it pass
- REFACTOR: Improve the code while keeping tests green
Test Structure
- Follow arrange-act-assert (AAA) pattern — makes test intent immediately clear
- One assertion per test when practical
- Use descriptive names: “should [behavior] when [condition]”
- Keep tests independent — no shared mutable state between tests
BAD: Test B relies on data created by Test A GOOD: Each test creates its own data in the arrange phase
What to Test
- Business logic and domain rules
- Edge cases: empty inputs, nulls, boundaries, overflow — most bugs hide at boundaries
- Error paths: invalid input, network failures, timeouts
- Integration points: API endpoints, database operations
What NOT to Test
- Framework internals or third-party library behavior — trust the library, test your usage
- Trivial getters/setters with no logic
- Implementation details (test behavior, not structure) — implementation-coupled tests break on every refactor
BAD: Assert that an internal method was called 3 times GOOD: Assert that the output matches the expected result
Contract Testing
- Use consumer-driven contract tests (Pact or similar) between services — catches integration breaks before deployment
- Each consumer defines its expected API contract; the provider verifies it in CI
Realistic Test Infrastructure
- Use Testcontainers for integration tests that need real databases, message brokers, or caches — mocking persistence hides real bugs
- Reserve mocks for truly external services (third-party APIs, payment gateways)
Mocking
- Mock only external dependencies (APIs, third-party services)
- Do not mock the module under test — you would be testing the mock, not the code
- Prefer fakes and stubs over complex mock frameworks
- Reset mocks between tests to avoid leaking state
Advanced Techniques
- Use property-based testing for functions with large input spaces — the framework generates edge cases you would not write by hand
- Use mutation testing periodically to verify test suite effectiveness — if mutants survive, tests are weak
- Avoid large snapshot tests — developers blindly update them instead of investigating failures; keep snapshots small and focused
Speed & Parallelism
- Run tests in parallel by default — ensure tests are isolated with no shared mutable state
- Keep the full test suite under 5 minutes — slow suites get skipped