Snapshot Testing Pitfalls: Why Developers Blindly Update Snapshots (And How to Stop)

Snapshot Testing Pitfalls: Why Developers Blindly Update Snapshots (And How to Stop)

There's a pattern that plays out on almost every team that adopts snapshot testing. It starts with good intentions: snapshots are easy to create, they run automatically, and they seem to provide coverage. Then a few months later, someone's blocked on a PR review because of failing snapshots they don't understand, and the instinct is to run jest --updateSnapshot and move on. Once that pattern takes hold, your snapshot suite stops catching regressions and starts generating false confidence.

Understanding why this happens — and the specific practices that prevent it — is more useful than a general "be careful with snapshots" warning.

Why the Blind Update Happens

The root cause is almost always one of a few anti-patterns. Each creates a situation where reviewing the snapshot diff is harder than just updating it.

Anti-pattern 1: Snapshots That Are Too Large

A snapshot of a full page component might be 400 lines of serialized HTML. When it fails, the diff shows 40 changed lines spanning multiple sections of the tree. Reviewing it properly requires understanding the entire component hierarchy, loading the browser, and tracing how the rendered output connects to the source.

Developers don't do this. They run the app locally, it looks fine, and they update the snapshot. The regression that the snapshot was supposed to catch — a collapsed section in a different viewport, an error state in a nested component — stays undetected.

The fix: Keep snapshots small. Snapshot individual components, not pages. If a component is too large to review as a snapshot, it's a signal to split the component or test its parts separately.

Anti-pattern 2: Dynamic Data in Snapshots

This is the most common source of snapshot noise. If your component renders anything time-dependent — timestamps, generated IDs, random values, date-relative strings like "3 minutes ago" — the snapshot fails on every run, even when nothing is wrong.

// Bad: will fail on every run
test('renders notification', () => {
  const { container } = render(<Notification message="Saved" time={Date.now()} />);
  expect(container).toMatchSnapshot();
});

After a few spurious failures, developers learn that snapshot failures don't mean real problems. They update reflexively.

The fix: Mock dynamic values before snapshotting.

// Good: stable timestamp
beforeEach(() => {
  jest.spyOn(Date, 'now').mockReturnValue(1700000000000);
});

test('renders notification', () => {
  const { container } = render(<Notification message="Saved" time={Date.now()} />);
  expect(container).toMatchSnapshot();
});

For IDs and other generated values, use deterministic test factories or mock the generation function.

Anti-pattern 3: Snapshot Creep

Snapshot creep is when your test suite accumulates snapshot assertions across every test — not just tests where snapshots provide value, but everywhere, because it became habit. Each component gets three or four snapshot assertions alongside its behavioral tests.

The problem isn't any individual snapshot; it's the volume. When dozens of snapshots fail after a CSS refactor — which is correct, because the CSS changed — the update burden is so high that teams establish an unofficial policy of updating without review.

The fix: Audit your snapshots quarterly. Ask for each one: "What specific regression would this catch that no other assertion covers?" If you can't answer, delete the snapshot and replace it with a targeted assertion.

Anti-pattern 4: Snapshot Tests Without Named States

Jest uses the test description as the snapshot key. When descriptions are generic — renders component, snapshot test, default state — reviewers can't tell from the failing test name what they're looking at.

// Bad: what state is this?
test('renders correctly', () => {
  expect(container).toMatchSnapshot();
});

// Good: state is explicit
test('renders empty cart state with prompt to add items', () => {
  expect(container).toMatchSnapshot();
});

Named states make snapshot diffs reviewable. When renders empty cart state with prompt to add items fails, you know exactly which part of the component to look at.

PR Review Guidelines for Snapshots

A snapshot change in a PR is a code change. It should be reviewed with the same scrutiny as a logic change. Establish these norms on your team:

Never approve snapshot updates without reading the diff. This sounds obvious but requires explicit team agreement. If your PR review process doesn't mention snapshots, they'll be rubber-stamped.

Require a comment explaining snapshot changes. PR authors should add a comment whenever they update snapshots: "Updated snapshot — changed the button to use role attribute instead of data-testid." If the author can't explain the change, they updated blindly.

Reject snapshot-only PRs that don't explain intent. A PR that updates 15 snapshots across unrelated components, with no explanation and no code changes in those components, is a red flag. Something changed — find out what.

Use CI to flag surprise snapshot failures. If snapshot tests fail in CI on a branch that wasn't expected to touch those components, that's a signal worth investigating before updating. Set up annotations or alerts for unexpected snapshot failures across unrelated test files.

Meaningful vs Meaningless Snapshots

The test for whether a snapshot is meaningful: would a developer reviewing the diff be able to determine whether the change is intentional?

Meaningful snapshots:

  • Small, focused components where the entire output is visible in the diff
  • Specific states that are hard to assert on with queries (complex tree structures, deeply nested conditionals)
  • Serialized configuration objects where structure matters
  • Server-rendered HTML where the full output is the contract

Meaningless snapshots:

  • Full page trees where diffs span hundreds of lines
  • Components with dynamic IDs, timestamps, or random values
  • Snapshots of test utilities or mocks rather than real components
  • Duplicate snapshots of the same state from different test cases

The "Would I Notice?" Test

Before creating a snapshot, ask: if this snapshot failed tomorrow because of a real regression, would I notice? Would the diff be readable enough to catch the problem?

If the component is large, the answer is probably no — a real regression would be buried in 40 changed lines. If the component is small and focused, yes — a changed class name or missing element would be obvious in a 5-line diff.

Apply the same test to existing snapshots. If your team wouldn't notice a regression in the diff, the snapshot isn't providing protection — it's providing the illusion of protection while adding maintenance overhead.

Building a Culture That Reviews Snapshots

The blind update problem is ultimately cultural. Snapshots get updated without review when teams don't have explicit norms about them. A few practices that help:

Demo snapshot diffs in team reviews. Once a quarter, pull up a recent snapshot change in a retrospective and walk through how you'd review it. Shared understanding of what to look for builds the habit.

Add snapshot review to PR checklists. If your team uses PR templates, add "snapshot changes reviewed and explained" as an explicit checkbox.

Celebrate caught regressions. When a snapshot catches a real regression — and it will — call it out. Make the value visible. Teams maintain practices that demonstrably work.

Set a snapshot size limit. Consider a linting rule or pre-commit hook that warns when a snapshot exceeds a certain line count (100 lines is a reasonable threshold). Large snapshots aren't useful; keeping them small is a forcing function for better component design.

Snapshot testing done well is a genuine asset. The teams that get value from it are the ones who treat snapshot failures as signals worth reading, not obstacles to update away.

Read more