Testing & QA

Snapshot testing — what it's good for and its real failure mode

A snapshot test doesn't encode any expectation of what's correct — its whole mechanism is 'did this change,' which is genuinely useful for one specific job and a serious liability when reached for as a general-purpose testing tool.

Intermediate

3 min read

What a snapshot test actually does

test("renders a user card", () => {
  const tree = render(<UserCard name="Ada Lovelace" role="Engineer" />);
  expect(tree).toMatchSnapshot();
});

The first time this test runs, the testing framework saves the rendered output to a file (__snapshots__/UserCard.test.js.snap) and passes automatically — there's no expected value written by a human anywhere. Every run after that compares the new output against the saved file: identical, the test passes; different, the test fails and shows a diff. Notice what's missing compared to every other kind of test in this domain: nobody ever wrote down what the correct output actually should be — the test just detects change, with zero built-in judgment about whether that change is a bug or an intended improvement.

The real failure mode: reflexive --updateSnapshot

FAIL  UserCard.test.js
  ✕ renders a user card

  - Snapshot
  + Received

  - <div class="user-card">
  -   <h2>Ada Lovelace</h2>
  + <div class="user-card broken-layout">
  +   <h2>Ada Lovelace</h2>

A developer facing this failure has two choices: actually read the diff and decide whether broken-layout is an intended class change or a real bug, or run the update-snapshot command and move on. Under time pressure — a large PR, a CI check blocking a merge, a snapshot test that's failed and been "fixed" this way several times before — the second option is very tempting, and it makes the failure disappear without anyone actually judging whether the new output is correct. A snapshot test that gets rubber-stamp-updated on every failure provides exactly zero real protection: it will happily "pass" a genuinely broken change, as long as someone approves the new snapshot without scrutiny.

What snapshot testing is actually good for: catching unintended structural change

Snapshot tests earn their keep specifically for things that are large, structural, and tedious to assert on field-by-field by hand — a big JSON API response shape, a large rendered component tree, a generated config file — where the real risk being guarded against is "did something change by accident" (an unrelated refactor accidentally altering output) rather than "is this specific field's exact value correct." For that job, a snapshot is genuinely efficient: one line of test code covers a large surface area that would otherwise take dozens of individual assertions to pin down.

The discipline that keeps snapshot tests trustworthy

Treat every snapshot diff exactly like a code review comment: read it, understand why the output changed, and only accept the new snapshot when that reason is a real, intended change — never as a reflex to make a red check turn green. Keeping snapshots small and focused (one component, one specific shape) rather than one enormous snapshot of an entire page also helps: a small, focused diff is easy to actually read and judge; a thousand-line diff on a whole-page snapshot is exactly the kind of thing that gets approved without being read at all.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What does a snapshot test actually verify, that makes it different from every other kind of test in this domain?

2. What's the real failure mode that makes snapshot tests a liability if used carelessly?

3. What is snapshot testing genuinely well-suited for?