QuestionsTesting

Snapshot testing: value, pitfalls, and rot

Snapshot TestingHardTesting

What is snapshot testing good and bad at? How do snapshots 'rot', and how do you use them responsibly?

What it tests

Whether you understand snapshots as change-detectors, not correctness checks, and can avoid the classic failure mode.

Approach & answer

A snapshot test renders something (a component, a serializable object) and saves its serialized output to a file on first run; subsequent runs DIFF the current output against the stored snapshot and fail if they differ. Its genuine value is CHANGE DETECTION with almost no assertion-writing: it alerts you when output changed unexpectedly, which is handy for catching accidental UI/markup regressions and for large structured outputs where hand-writing assertions is tedious. The critical mental model: a snapshot proves output DIDN'T CHANGE, not that it's CORRECT. The first snapshot is captured blind — if the component was already buggy, the snapshot enshrines the bug and happily 'passes'. That's the core weakness. The classic failure mode is snapshot ROT: large, whole-component snapshots change on almost every legitimate edit, producing constant diffs; developers stop reading them and reflexively run `jest -u` to update, at which point the snapshot tests nothing — they rubber-stamp whatever the code produces. Big snapshots also make diffs unreadable (hundreds of lines), so real regressions hide among noise. Responsible use: (1) keep snapshots SMALL and FOCUSED — snapshot a specific piece of output or use inline snapshots (toMatchInlineSnapshot) that live next to the test and get reviewed in the diff, not giant DOM dumps; (2) REVIEW every snapshot change in code review as deliberately as any assertion — an updated snapshot is a claim that the new output is correct; (3) prefer EXPLICIT assertions (getByRole, toHaveTextContent) for the behavior you actually care about, and use snapshots only as a supplementary net; (4) don't snapshot things that legitimately vary (dates, ids, random) without serializers/masks, or they'll be perpetually flaky; (5) treat a failing snapshot as a question ('did I mean to change this?'), and only update after confirming the new output is intended. The honest summary: snapshots are a cheap tripwire, not a substitute for asserting correctness — their value collapses the moment updating them becomes reflexive.

Use this technique when

Deciding whether to snapshot; reviewing a PR full of updated snapshots; taming snapshot rot.

Code

// Rot risk: a giant blind snapshot of a whole component
expect(render(<Dashboard />).container).toMatchSnapshot(); // 300-line diff, rubber-stamped

// Better: small, reviewable, inline
expect(formatMoney(1999)).toMatchInlineSnapshot('"$19.99"');

// Best for behavior you care about: an explicit assertion
expect(screen.getByRole('status')).toHaveTextContent('Saved');

// Rules: keep snapshots tiny, REVIEW every update, don't reflexively run 'jest -u'.

References