Testing trophy end-to-end few,… integration most… unit pure… static type… A test portfolio weighted toward integration tests, on a base of static checks, with fewer unit and end-to-end tests at the edges.
in practice Kent C. Dodds' alternative to the test pyramid for UI code; most confidence per minute from integration tests.
Unit test test('formats kobo as naira', () => { expect(formatNGN(150000)).toBe('₦1,500.00'); }); A test of one function or module in isolation, fast and deterministic.
in practice Formatters, reducers, validators, money arithmetic; Vitest or Jest.
Integration test (component) render <… user typ… userEvent MSW answ… mock API assert s… A test rendering a component with its real children and a mocked network, driven like a user.
in practice Testing Library + MSW; "fill the form, click pay, see the receipt".
End-to-end test browser Playwright preview URL real app real or seed… A test driving a real browser through the deployed app, from login to the outcome.
in practice Playwright and Cypress suites against preview deployments; the critical-path smoke tests.
Testing Library getByRole role + accessible name getByLabelText form fields getByText non-interactive text getByTestId last resort Helpers that query the DOM the way users and assistive tech find things (by role, label, text), not by implementation details.
in practice getByRole("button", {name: /pay/i}); tests that survive refactors and double as accessibility checks.
Mock Service Worker component fetch MSW GET /api/cart intercepted { items: [...] } Intercepting network requests at the network layer with handlers, so tests and dev use the real fetch code against fake responses.
in practice MSW handlers shared by tests, Storybook and local development.
Test double (mock, stub, spy, fake) stub returns canned data spy records calls mock asserts calls fake working lightweight version Stand-ins for real dependencies: stubs return canned values, spies record calls, mocks assert expectations, fakes are working light implementations.
in practice vi.fn(), an in-memory repository, a fake clock.
Fake timers vi.useFakeTimers(); search("pay"); vi.advanceTimersByTime(300); expect(fetch).toHaveBeenCalledOnce(); Replacing setTimeout, Date and friends with a controllable clock so time-based code can be tested instantly and deterministically.
in practice Debounce, retries with backoff, session expiry tests.
Snapshot test big snapshot 400 lines of DOM "update all" tests nothing inline, small toMatchInlineSnapshot one value reviewed Saving serialised output and failing when it changes; cheap to write, easy to rubber-stamp.
in practice Large component snapshots updated blindly; small inline snapshots for serialisers are fine.
Contract test frontend contract backend expects shape verifies shape CI fails on drift Checking that a consumer and provider agree on an API's shape, so frontend and backend can change independently without breaking each other.
in practice Pact; OpenAPI schemas validated in both repos; generated typed clients.
Property-based test fc.assert(fc.property(fc.array(fc.integer()), xs => sum(xs) === sum([...xs].reverse()))); // failure shrinks to e.g. [1, -1] Generating many random inputs to check that a property always holds, and shrinking failures to the smallest example.
in practice fast-check for parsers, money math, sorting, CRDT merges.
Mutation testing if (amt > limit) original if (amt >= limit) mutant killed ✓ return fee * 1 mutant survived ✗ Changing the code in small ways (flipping a > to >=) and checking that some test fails; surviving mutants mark untested logic.
in practice Stryker; a better signal than line coverage for critical code.