| name | testing-pyramid |
| description | Use when implementing tests for a feature. Guides test strategy across unit/integration/e2e layers based on what the feature does. Required for the implementer agent. |
Testing Pyramid
Your job: choose the right test types and write the right number of tests at each layer.
The Pyramid
▲
╱ ╲ E2E (slow, brittle, expensive)
╱___╲ 1-3 per major user flow
╱ ╲
╱_______╲ Integration (medium speed, real boundaries)
╱ ╲ 3-10 per feature
╱___________╲
╱ ╲ Unit (fast, focused, cheap)
╱_______________╲ many — every meaningful function/class
More tests at the bottom, fewer at the top. The pyramid is inverted only if you have e2e tests covering things that should be unit tests.
Choosing the Right Layer
Unit tests
Test pure logic in isolation. No I/O, no DB, no network, no real time.
When to write:
- Pure functions (formatters, validators, calculators)
- Class methods with deterministic behavior
- State transitions (reducers, state machines)
- Edge cases of any algorithm
When NOT:
- Glue code that just wires things together (covered by integration)
- Code whose value is in interaction with externals (DB, API)
Integration tests
Test how your code interacts with real boundaries — DB, file system, internal services. Mock only external services you don't control.
When to write:
- Repository methods (touches real DB)
- API endpoints (touches HTTP layer + DB)
- File processing pipelines (touches FS)
- Service-to-service communication (touches in-process services)
Key principle: the most valuable integration tests cover the boundaries between your code and someone else's — the DB, the message broker, the third-party API. That's where bugs hide.
End-to-end tests
Test the full system from the user's entry point. Real UI, real backend, real DB. Few but critical.
When to write:
- Critical happy paths (user signup, checkout, primary feature usage)
- Critical regression risks (the thing that, if broken, takes the product down)
When NOT:
- Unit-level concerns (use unit tests instead)
- Edge cases (use integration or unit, e2e is too slow)
- "I don't trust my unit tests" (fix the trust, not the test layer)
Test Naming
Use behavior-describing names. The test name should explain what's being tested AND the expected outcome:
✗ test_user_creation
✓ test_creating_user_with_duplicate_email_returns_conflict_error
✗ test_auth
✓ test_jwt_with_expired_signature_is_rejected
The "what to test" Heuristic
For a given function/method/endpoint, ask:
- Happy path: with valid input, does it produce the expected output? (always test)
- Boundary inputs: empty strings, zero, negative numbers, max integer, etc.? (test if relevant)
- Invalid inputs: malformed data, wrong types, missing fields? (test the error contract)
- Side effects: does it write where it should and not where it shouldn't?
- Error paths: what happens when downstream things fail?
If the function is pure: 1, 2, 3 are enough.
If the function has side effects: add 4.
If the function depends on others: add 5.
Anti-patterns
- Testing implementation, not behavior: tests that break when you refactor without changing behavior
- One assertion per test: usually wrong — group related assertions about the same scenario
- Mocking everything: tests pass but production breaks because you mocked away the bug
- Snapshot tests for non-UI code: brittle, hide bugs, hard to review
- Test code with no asserts: literally not testing anything
- Testing the framework: don't test that React renders, that Express routes work — test YOUR code
When Acceptance Criteria Mention Tests
If PLAN.md says "tests pass" as an AC, you must:
- Decide which layers each AC needs (often a mix)
- Write the tests
- Run them, confirm they pass
- Include the test command + output in SUMMARY.md
If PLAN.md says specifically "unit tests for X" or "integration test for Y", honor that exactly.
Test File Conventions
Follow the project's existing convention. If none exists:
- Unit tests: next to the file (
user.ts → user.test.ts)
- Integration tests:
tests/integration/
- E2E tests:
tests/e2e/
Don't introduce new conventions without checking with the orchestrator.
What Goes in the SUMMARY
Mention:
- Which test files were created/modified
- The command that runs them
- The pass/fail count
- Any test that was skipped (with reason)
Example:
## Tests
Created: `src/services/auth.test.ts` (8 tests)
Created: `tests/integration/auth-flow.test.ts` (3 tests)
Run: `npm test src/services/auth.test.ts tests/integration/auth-flow.test.ts`
Result: 11 passed, 0 failed, 0 skipped
Verified: AC-01, AC-02, AC-03 (login flow), AC-04 (refresh)