Fix: Jest Snapshot Outdated — 1 snapshot obsolete
Part of: JavaScript & TypeScript Errors
Quick Answer
How to fix Jest snapshot failures — updating outdated snapshots, removing obsolete ones, fixing inline snapshots, preventing brittle snapshot tests, and managing snapshots in CI.
The Error
Jest fails with a snapshot mismatch:
● MyComponent › renders correctly
1 snapshot failed.
Snapshot name: `MyComponent renders correctly 1`
- Snapshot - 3
+ Received + 3
<div
className="container"
- style="color: red;"
+ style="color: blue;"
>
- <h1>Old Title</h1>
+ <h1>New Title</h1>
</div>Or obsolete snapshots after deleting tests:
› 1 snapshot obsolete.
• MyComponent renders the old variant 1
Snapshot Summary
› 1 snapshot obsolete from 1 test suite. Re-run Jest with `--updateSnapshot` to remove it.Or in watch mode:
Snapshot Summary
› 3 snapshots failed from 2 test suites. Inspect your code changes or re-run jest with `-u` to update them.Why This Happens
Jest snapshot tests compare the current output of a component or function against a previously saved .snap file. On the first run, Jest writes the output to disk and the test passes. On every subsequent run, Jest serializes the new output, normalizes whitespace, and does a strict string compare against the stored snapshot. Any difference — even a trailing space or a renumbered React key — counts as a failure.
The serializer is the key to understanding why “the same code” produces different snapshots across machines. Jest ships with built-in serializers for plain objects, React elements (via react-test-renderer or @testing-library/react), and a few other types. Plugins like enzyme-to-json or jest-serializer-vue register additional serializers, and a snapshot taken with one serializer cannot be replayed with another. If your CI fails on snapshots that pass locally, check that snapshotSerializers in jest.config.js is identical, that the React renderer version matches, and that no developer has stray local serializers loaded through a personal config.
The failure modes break down to a small set:
- Intentional code change — you updated a component’s markup, styles, or output, and the snapshot reflects the old version. The snapshot needs to be updated to match the new correct output.
- Unintentional regression — a code change accidentally broke something. The snapshot is correctly catching the regression.
- Obsolete snapshot — a test was renamed, moved, or deleted, but the old snapshot entry remains in the
.snapfile. - Dynamic content in snapshot — timestamps, random IDs, or other dynamic values differ on each run, causing snapshots to always fail.
- Environment differences — snapshot generated on one OS or Node version differs slightly from the current environment (rare, usually affects date formatting or whitespace).
Before blindly running --updateSnapshot, determine whether the change is intentional or a bug.
Version History That Changes the Failure Mode
Jest’s snapshot behavior has evolved across major releases, and an upgrade can quietly change what your existing snapshots look like. Jest 27 (May 2021) made the jest-circus test runner the default, replacing the older jest-jasmine2. Circus runs hooks and assertions in a slightly different order, which can affect which snapshots are written when beforeEach modifies module state. If you upgraded from 26 to 27+ and all your snapshots regenerated with subtle differences, the runner change is probably why. You can revert by setting testRunner: 'jest-jasmine2', but the package is no longer maintained.
Jest 28 (April 2022) extracted jest-environment-jsdom and jest-environment-node into separate packages that you must now install explicitly. Projects that rendered React components into a snapshot started seeing failures immediately after upgrade because the default environment became node and document was undefined. The fix is npm install --save-dev jest-environment-jsdom and setting testEnvironment: 'jsdom' in the config. Snapshots taken under jsdom have specific element ordering and attribute formatting that differs from Node-only renders.
Jest 29 (August 2022) raised the minimum Node version to 14 (14.15 specifically) and tightened the serializer for Map and Set objects. Snapshots containing those types may need a one-time regeneration. Jest 30 is in preview at the time of writing and brings further default changes; pin your major version in package.json and read the changelog before bumping. For projects migrating to Vitest, the good news is that Vitest’s snapshot format is intentionally compatible with Jest’s — .snap files transfer with minimal edits, and toMatchInlineSnapshot works the same way. The serializers differ slightly, so expect some regeneration on the first run after migration.
Fix 1: Update Outdated Snapshots (Intentional Changes)
When you’ve intentionally changed a component and the snapshot is just stale:
# Update all failing snapshots
jest --updateSnapshot
# Or the short form
jest -u
# Update snapshots for a specific test file
jest MyComponent.test.tsx --updateSnapshot
# Update in watch mode — press 'u' when prompted
jest --watch
# Then press 'u' to update all failing snapshots
# Or press 'i' to update them one by one interactivelyAfter updating, review the changes in the .snap file with git diff before committing:
git diff src/__snapshots__/This confirms the snapshot changes are exactly what you intended — not an accidental regression.
Common Mistake: Running
jest -uto “fix” CI failures without reviewing what changed. Snapshot tests only catch regressions if you read the diff before accepting it. An unreviewedjest -ucommit is an untested change.
Fix 2: Remove Obsolete Snapshots
Obsolete snapshots are entries in .snap files that no longer correspond to any test. They accumulate when tests are renamed or deleted:
# Remove obsolete snapshots only (doesn't update failing ones)
jest --updateSnapshot
# Or explicitly
jest --ci && jest -u # Will only remove obsolete, update changedManually clean up a .snap file if --updateSnapshot doesn’t help:
// src/__snapshots__/MyComponent.test.tsx.snap
// Delete entries that no longer have corresponding testsIn watch mode, press u to update all snapshots including removing obsolete ones.
Fix 3: Fix Dynamic Values in Snapshots
Snapshots with dynamic content (timestamps, random IDs, generated hashes) fail on every run:
// BAD — snapshot contains the current date, fails tomorrow
test('renders invoice', () => {
const invoice = <Invoice date={new Date()} id={Math.random()} />;
expect(invoice).toMatchSnapshot();
// Snapshot: date="2026-03-19T10:23:45.123Z" id="0.8472651..."
// Tomorrow: date="2026-03-20T..." ← FAILS
});Fix — mock or freeze dynamic values:
// Mock Date to return a fixed value
jest.useFakeTimers();
jest.setSystemTime(new Date('2026-01-01'));
test('renders invoice', () => {
const invoice = <Invoice date={new Date()} id="fixed-test-id" />;
expect(invoice).toMatchSnapshot();
});
afterEach(() => {
jest.useRealTimers();
});Use expect.any() matchers in snapshots for fields that legitimately vary:
test('creates user', () => {
const user = createUser({ name: 'Alice' });
expect(user).toMatchSnapshot({
id: expect.any(String), // Matches any string — ID is dynamic
createdAt: expect.any(Date), // Matches any date
name: 'Alice', // Exact match for stable fields
});
});Use toMatchInlineSnapshot for small outputs — inline snapshots live in the test file and are easier to review:
test('formats price', () => {
expect(formatPrice(9.99)).toMatchInlineSnapshot(`"$9.99"`);
// Jest writes the snapshot directly into the test file on first run
});Fix 4: Use More Specific Assertions Instead of Full Snapshots
Snapshot tests that capture entire component trees are brittle — any trivial change (adding a CSS class, changing a div to a section) breaks them. Use targeted assertions for stable properties:
// BAD — entire component snapshot breaks on any markup change
test('shows username', () => {
render(<UserCard user={{ name: 'Alice', role: 'admin' }} />);
expect(screen.getByRole('article')).toMatchSnapshot();
});
// BETTER — assert on specific behavior, not the full DOM
test('shows username', () => {
render(<UserCard user={{ name: 'Alice', role: 'admin' }} />);
expect(screen.getByText('Alice')).toBeInTheDocument();
expect(screen.getByText('admin')).toBeInTheDocument();
});
// GOOD USE OF SNAPSHOTS — for complex computed output
test('serializes config object', () => {
const config = buildConfig({ env: 'production', debug: false });
expect(config).toMatchSnapshot();
// Snapshot captures the exact config structure — intentional
});Snapshots are most valuable for:
- Complex data transformation output (serializers, formatters)
- CLI output text
- API response shapes (with dynamic fields masked)
- Error message formats
Snapshots are least valuable for:
- Simple component rendering (use
@testing-library/reactqueries instead) - Anything with dynamic content you don’t control
Fix 5: Fix Snapshot Location and Naming
Snapshots are stored in __snapshots__/ directories next to the test files. If tests are moved without moving the snapshots, obsolete entries accumulate:
src/
components/
Button.test.tsx ← test file
__snapshots__/
Button.test.tsx.snap ← auto-generated snapshot fileIf you rename a test file, Jest creates a new snapshot file and the old one becomes orphaned:
# Find orphaned snapshot files
find . -name "*.snap" -not -path "*/node_modules/*"
# Then check if corresponding test files existName snapshots descriptively to make diffs readable:
// Jest uses the test description as the snapshot name
test('renders button in primary variant', () => {
// Snapshot name: "renders button in primary variant 1"
expect(render(<Button variant="primary">Click</Button>).asFragment())
.toMatchSnapshot('primary button'); // Custom name
});Fix 6: Handle Snapshots in CI
In CI, never auto-update snapshots — fail explicitly so the developer must review the change:
# CI script — fail if any snapshot is outdated or obsolete
jest --ci
# --ci flag:
# - Fails if snapshots need updating (doesn't auto-update)
# - Fails if there are obsolete snapshots
# - Disables interactive mode# GitHub Actions example
- name: Run tests
run: jest --ci --coverageIf CI fails on snapshots, the fix is to update them locally and commit the updated .snap files:
# Locally
jest -u # Update all
git add src/__snapshots__/
git commit -m "Update snapshots for Button refactor"Pro Tip: Add
.snapfiles to your PR review checklist. Snapshot diffs often reveal unintended changes — a snapshot growing by 20 lines might mean a component is now rendering unexpected content.
Fix 7: Regenerate All Snapshots from Scratch
If snapshots are severely out of date across many files, regenerate them all:
# Delete all snapshot files
find . -name "*.snap" -not -path "*/node_modules/*" -delete
# Run tests — all snapshots fail because no .snap files exist
# Jest creates fresh snapshots automatically
jest -uReview the newly created snapshots carefully with git diff before committing.
Still Not Working?
Check for non-deterministic rendering. If a snapshot fails inconsistently, the component output varies between renders. Common causes:
- Async data loading without proper mocking
- CSS-in-JS libraries that generate class names with counters
- Third-party components with internal state
Serialize custom objects. If your snapshot shows [Object] instead of the actual content, Jest doesn’t know how to serialize it. Add a custom serializer:
// jest.config.js
module.exports = {
snapshotSerializers: ['enzyme-to-json/serializer'],
// Or for custom types:
// snapshotSerializers: ['./src/test/myCustomSerializer'],
};Reset snapshot counters. Each toMatchSnapshot() call in a test gets a numbered name (snapshot 1, snapshot 2). If you add a snapshot call before existing ones, all subsequent numbers shift and every snapshot fails. Use explicit names:
// Without name — fragile, renumbers on insertion
expect(a).toMatchSnapshot(); // "renders correctly 1"
expect(b).toMatchSnapshot(); // "renders correctly 2"
// With name — stable
expect(a).toMatchSnapshot('initial state');
expect(b).toMatchSnapshot('after click');Check the line endings on Windows. Snapshots committed from a macOS or Linux machine use \n line endings. If a Windows developer’s editor or git config rewrites them to \r\n, every line-based diff reports a mismatch. Add *.snap text eol=lf to .gitattributes and re-normalize the repository to force LF endings on all platforms.
Inspect the snapshot serializer list. Run Jest with --showConfig and look at the snapshotSerializers array. If a serializer was added but not committed (or the package was reinstalled without dev dependencies in CI), snapshots regenerate with a different shape every run. Make sure every entry in that array is in package.json under dependencies or devDependencies, not just installed locally.
Watch for React 18’s automatic batching changes. React 18 batches more state updates by default than React 17 did. Tests that called act() and then inspected component output may see snapshots taken at a slightly different point in the render lifecycle. Wrapping side-effectful code in await act(async () => { ... }) produces deterministic snapshots across versions.
For related testing issues, see Fix: Jest Test Timeout, Fix: Jest Mock Not Working, Fix: Jest Cannot Find Module, and Fix: Vitest Setup Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Jest Setup File Not Working — setupFilesAfterFramework Not Running or Globals Not Applied
How to fix Jest setup file issues — setupFilesAfterFramework vs setupFiles, global mocks not applying, @testing-library/jest-dom matchers, module mocking in setup, and TypeScript setup files.
Fix: Jest Async Test Timeout — Exceeded 5000ms or Test Never Resolves
How to fix Jest async test timeouts — missing await, unresolved Promises, done callback misuse, global timeout configuration, fake timers, and async setup/teardown issues.
Fix: Jest Coverage Not Collected — Files Missing from Coverage Report
How to fix Jest coverage not collecting all files — collectCoverageFrom config, coverage thresholds, Istanbul ignore comments, ts-jest setup, and Babel transform issues.
Fix: Jest Fake Timers Not Working — setTimeout and setInterval Not Advancing
How to fix Jest fake timers not working — useFakeTimers setup, runAllTimers vs advanceTimersByTime, async timers, React testing with act(), and common timer test mistakes.