Test Trophy
Kent C. Dodds' testing trophy puts integration tests at the centre, static analysis at the base — and explains when to reach for each layer.
The shape
The testing trophy inverts the pyramid’s emphasis: static analysis at the base, integration tests as the largest investment, unit tests narrower, E2E tests at the top.
▲
E2E
───────
Unit Tests
─────────────
Integration Tests ← biggest layer
─────────────────────
Static ← type checker, linter (free confidence)
The four layers
Static analysis — TypeScript, ESLint. Zero runtime cost. Catches type mismatches, undefined variables, unreachable code. Every TypeScript project gets this for free.
Integration tests — test a module with its real dependencies (real database, real file system, real HTTP calls mocked at the network boundary). High confidence per test because nothing is stubbed. Playwright’s component tests live here.
Unit tests — test one function or class with all dependencies mocked. Ultra fast. High volume. Low per-test confidence because real behaviour is substituted. Valuable for pure algorithmic logic; overused for everything else.
E2E tests — full browser, real backend, real database. Maximum confidence, maximum cost. Keep the count low and the scenarios high-value (happy path + critical failures).
Pyramid vs trophy — when each model fits
Use the pyramid when:
- You have a large, well-bounded backend (lots of pure logic worth unit-testing)
- CI time is measured in seconds
Use the trophy when:
- Your product is primarily UI-driven
- You cannot meaningfully unit test UI components in isolation
- Integration tests against a real DOM give you far more confidence per investment than mocked unit tests
Playwright projects usually follow the trophy: static (TypeScript strict mode) + a healthy integration layer (Playwright component tests or API tests) + a thin E2E layer.
Static analysis is not optional
// Without TypeScript, this ships silently:
const user = getUser(123);
console.log(user.naem); // typo — runtime error
// With TypeScript strict:
// Property 'naem' does not exist on type 'User'. Did you mean 'name'?