Jest Snapshot Failures: A Diagnostic Guide from Diff to Fix
Treat a Jest snapshot failure as a diagnostic signal: read the diff, check the .snap file is committed, pin volatile data, and update snapshots deliberately.
19 Jul 2026, 12:17 UTC

A snapshot test that passed yesterday fails in CI today, the diff shows a timestamp where a slightly different timestamp used to be, and nobody touched the component. Before reaching for jest -u, treat the failure as a diagnostic signal: the diff plus a handful of ordered checks narrows nearly every Jest snapshot failure to one of six causes, and only one of them is actually fixed by updating snapshots. This guide walks the diagnosis in order, with the commands to run and the fix that matches each finding.
What a snapshot failure actually reports
Jest stores each snapshot as serialized text in a __snapshots__ directory next to your test file, keyed by the test's name. On every run it re-serializes the current value and compares it against the stored text. A failure means one of three things: the current output differs from the stored snapshot, the snapshot is missing (which fails under --ci), or Jest could not read or write the file. The .snap file is plain text and part of your code review surface — which is why the diff is the first diagnostic, not -u.
Cause table: read the failure before touching anything
| Clue in the failure output | Likely cause | First action |
|---|---|---|
| Diff contains dates, UUIDs, or random IDs | Volatile data in rendered output | Pin the value with fake timers or mocks |
| Markup or attribute changes right after a dependency upgrade | Legitimate render change | Review the diff, then a scoped -u |
| Passes locally, "snapshot not found" behavior in CI | .snap file not committed | Check git tracking and .gitignore |
| "obsolete snapshot" lines in the run summary | Renamed or deleted tests | Delete stale entries after confirming |
| Fails on one machine or CI only, markup-level diffs | Environment or transform mismatch | Compare resolved config; clear cache |
| EACCES, EPERM, or read-only filesystem errors | Write permissions on __snapshots__ | Fix writability for the user running Jest |
Ordered checks
- Classify the diff. Read it before running anything, and sort what you see into three buckets: volatile values (timestamps, IDs, random data), intentional markup (changes matching a real code or dependency change), and everything else (reordered attributes, extra nodes, whitespace). If re-running the test produces a different diff each time, you are looking at volatile data — go straight to the first fix. Run from your project root, no special permissions required:
Expected: the same single test runs each time; a diff that changes between two runs confirms nondeterminism.npx jest src/components/Card.test.tsx -t 'renders banner' - Confirm the snapshot file is committed. Both commands are read-only git queries:
Expected:git ls-files '*__snapshots__*' git check-ignore -v src/components/__snapshots__/Card.test.tsx.snapls-fileslists the failing test's.snapfile. If it does not,check-ignoreprints the rule hiding the path; no output fromcheck-ignoremeans nothing is ignoring it. A gitignored snapshot suite is worse than a failing one: without--ci, Jest writes missing snapshots and the tests pass, so the suite can stay green while catching nothing. - Compare environments when the failure is machine-specific. Check
testEnvironmentin your Jest config and whether the file uses a@jest-environmentdocblock. Since Jest 28, jsdom support comes from the separatejest-environment-jsdompackage, which should match your Jest major version. To compare a passing and a failing machine, runnpx jest --showConfigon both and diff thetestEnvironmentandtransformsections. - Rule out cache corruption.
npx jest --clearCachedeletes Jest's transform cache of compiled module code; it does not touch snapshot data, which is read from disk on every run. Stale transforms can still make code behave like an older version, so clearing is a cheap rule-out. Jest rebuilds the cache on the next run; no rollback is needed. - Check write permissions only if the error names EACCES, EPERM, or a read-only filesystem.
ls -ld src/components/__snapshots__should show a directory writable by the user running Jest. On CI containers this is commonly a checkout owned by root while tests run as a different user; fix the ownership rather than running tests as root.
Fixes matched to each finding
Volatile data: pin it, then re-snapshot once
Pin the source of nondeterminism instead of accepting a new snapshot every run. For time, use Jest's modern fake timers:
// Card.test.tsx — run from the project root
beforeEach(() => {
jest.useFakeTimers().setSystemTime(new Date('2026-01-15T10:30:00Z'));
});
afterEach(() => {
jest.useRealTimers();
});
With the pin in place, regenerate the snapshot once using the scoped update below and commit it; future runs are then stable. For random IDs, mock the generator module or spy on the function; for volatile fields buried in large objects, register a serializer with expect.addSnapshotSerializer that strips them. Caveat: fake timers can break code under test that depends on real asynchronous timing, so scope them to the affected tests and always restore real timers.
Intentional changes: review, then a scoped update
When the diff matches a real change — yours or a dependency upgrade — update deliberately. Scope the update to one file so -u cannot silently accept unrelated mismatches:
npx jest src/components/Card.test.tsx -u
-u accepts every mismatch in the files it runs, so read git diff on the .snap file before committing, and commit the source change and the snapshot together. Because this modifies files, the rollback is git restore src/components/__snapshots__/Card.test.tsx.snap if the diff shows something you did not expect.
Snapshots missing from version control
Remove any ignore rule matching __snapshots__ or *.snap, then git add the files. To make this class of failure loud instead of silent, run CI with the --ci flag: under it, a missing snapshot fails the test instead of being written. A typical CI step becomes jest --ci.
Obsolete snapshot warnings
Renaming a test or changing its title orphans the old key in the .snap file, and Jest reports it as obsolete in the summary. Jest does not prune these automatically; once you confirm no test references them, delete the stale keys — or the whole file if every entry is stale — in the same commit as the rename.
Environment and transform mismatches
If markup-level diffs appear on only one machine, align the jest-environment-jsdom version with your Jest major, set testEnvironment explicitly (or per file with /** @jest-environment jsdom */), and re-run after --clearCache. If the failure survives a clean clone, the remaining suspect is transform configuration (Babel or TypeScript), which you compare via --showConfig output from both machines.
When to escalate
Escalate rather than force-update when: the diff is hundreds of lines and unreadable — the snapshot is too coarse, and splitting the test or adding serializers is a design change that needs the component owner's input; the failure survives a clean clone, a cache clear, and matching environments — bring --showConfig output from both machines to whoever owns the test setup; Jest reports timeouts or "did not exit" warnings — run npx jest --detectOpenHandles on the single failing file (it is slow) and hand the reported handles to the setup's owner; or you cannot personally verify that a diff is intended. Snapshot review is a human judgment call; -u is not a decision-maker.
Limitations and assumptions
Examples assume Jest 29.x with a default configuration and jsdom supplied by jest-environment-jsdom. The flags used (-u, --ci, --clearCache, --detectOpenHandles, --showConfig) have been stable across recent major versions, but confirm against your version's documentation. Nothing here was executed against a live repository; the commands reflect standard CLI and git behavior, and the expected results described are what those tools document. Two boundaries matter: snapshots prove that output did not change, not that it is correct — a suite that auto-writes snapshots can be green and meaningless; and inline snapshots (toMatchInlineSnapshot) write into the test file and require Prettier, making their failure modes a separate topic not covered here.
How to verify the result
Close the loop with four checks: the previously failing test passes twice in a row locally; the full suite runs green with zero obsolete-snapshot warnings in the summary; git status shows only the .snap and source changes you intended; and CI runs with --ci and passes without writing any snapshot. If the third check fails, the scoped update accepted something you did not review — restore the file and start again at the diff.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.