Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    React Testing Strategy

    Put logic in plain functions and unit-test them; test hooks and pages as integration tests against a mocked network; assert only on what users can observe.

    Evergreentended planted 2 min read
    On this page

    The testing strategy I’ve settled on for React apps is short enough to fit on a sticky note: logic in services gets unit tests; hooks and pages get integration tests against a mocked network; every assertion is about observable behaviour.

    The layers

    LayerTest typeWhy
    Plain functions — transforms, validation, business rulesUnitFast, simple, no React needed
    Hooks, components, whole pagesIntegration with Testing Library + MSW (Mock Service Worker)Exercises real data fetching, state and rendering together
    Critical user journeysEnd-to-end with PlaywrightProves the deployed thing works

    Most of the confidence comes from the middle row. That’s the “testing trophy” idea from Kent C. Dodds: weight your effort towards integration tests.

    Rules I hold to

    • Test what the user observes, not implementation details. Query by role and label; assert on text and behaviour, not on state variables or which hook was called.
    • Move logic out of components so it’s trivially unit-testable. Don’t write extra code purely so something can be tested in isolation.
    • Mock only what you don’t control. Mock the network at the boundary. Never mock in-memory stores like Zustand or Redux — reset them between tests instead.
    • Reuse tested components. A shared component that’s already tested doesn’t need re-testing in every feature. (AI assistants often miss this, because they don’t know which components exist.)
    • Watch effect dependencies. Depend on query.data, not the whole query object, which changes identity every render.

    Snapshot tests: cheap change detectors

    Snapshots are easy to update blindly, so they guard against unnoticed change, not wrong change. Their real value is in code review: the list of changed snapshots tells a reviewer exactly which components a change touched. Keep them small and focused so people actually read the diff.

    The process side

    A large merge request without tests can’t be merged safely, however good the code looks. More than once I’ve found it cheaper to reject a big untested change and redo it as several small, tested ones. And an honest confession: on big tickets I’m tempted to push before writing tests and docs. Writing them while the understanding is fresh is far cheaper than coming back later.

    In practice

    This strategy took a team from patchy coverage to high, meaningful coverage on several services without brittle mocks — see A Testing Strategy Teams Actually Adopted. The broader principle of catching problems early and cheaply is Shift Left Testing.

    • #testing
    • #react
    • #testing-library
    • #integration-tests
    • #msw

    ↪ Links to (5)