Snapshot Testing with Jest: How It Works and When to Use It
Snapshot testing is one of those features that sounds like a silver bullet — write one line of code, and Jest will catch any unexpected change in your output. In practice, it works brilliantly for some scenarios and creates noise in others. Understanding the mechanism helps you decide when to reach for it.
How Jest Snapshots Work
When you call toMatchSnapshot() for the first time, Jest serializes the value you pass and writes it to a .snap file alongside your test. On subsequent runs, Jest compares the current output to the stored snapshot and fails the test if anything differs.
// Button.test.js
import { render } from '@testing-library/react';
import Button from './Button';
test('renders primary button', () => {
const { container } = render(<Button variant="primary">Save</Button>);
expect(container).toMatchSnapshot();
});After the first run, Jest creates __snapshots__/Button.test.js.snap:
// Jest Snapshot v1, do not edit
exports[`renders primary button 1`] = `
<div>
<button
class="btn btn--primary"
type="button"
>
Save
</button>
</div>
`;Any future change to that rendered output — a class name, an attribute, added whitespace — will fail the test. You then decide: was the change intentional or a regression?
Inline Snapshots
Inline snapshots store the snapshot directly in your test file instead of a separate .snap file. They're useful for small, readable outputs where you want the assertion visible without hunting through another file.
test('formats currency', () => {
expect(formatCurrency(1234.5, 'USD')).toMatchInlineSnapshot(`"$1,234.50"`);
});Jest writes the expected value directly into the source file on first run. Inline snapshots work well for string output, error messages, and small objects — anything where the snapshot itself communicates intent.
Updating Snapshots
When you make an intentional change to a component and your snapshots fail, run:
jest --updateSnapshot
# or the short form
jest -uThis rewrites all failing snapshots to match current output. The critical discipline here: only run --updateSnapshot after you've reviewed the diff and confirmed every change is intentional. The failure is the signal — updating blindly defeats the purpose.
To update snapshots for a single test file:
jest Button.test.js --updateSnapshotCustom Serializers
By default, Jest uses pretty-format to serialize values. You can add serializers to control how specific types appear in snapshots. The most common use case is styled-components:
// jest.config.js
module.exports = {
snapshotSerializers: ['@emotion/jest/serializer'],
};Without a serializer, styled-components snapshots show generated class names like sc-abc123 that change on rebuild. With the serializer, they show the actual CSS rules, making snapshots stable and readable.
When Snapshots Help
Serialized output you don't control directly. Configuration objects, API response shapes, and complex data structures that you want to track over time are good snapshot candidates. If something changes unexpectedly, the snapshot fails.
Catching accidental regressions in templates. Large HTML templates, email templates, and server-rendered pages benefit from snapshot coverage. The snapshot acts as a tripwire — changes show up immediately in CI.
Rapid coverage for legacy code. When you're adding tests to an existing codebase with no coverage, snapshots let you establish a baseline quickly. They don't test behavior, but they catch structural changes while you build out proper assertions.
When Snapshots Hurt
Component snapshots that change constantly. If you're iterating fast on a component's appearance, every style or markup change breaks the snapshot. You spend more time running --updateSnapshot than actually finding bugs.
Large, deeply nested snapshots. A snapshot of an entire page component is almost unreviable. When it fails, developers update it without reading — which means it stops catching regressions entirely.
Dynamic data. Dates, IDs, random values, and anything that changes between runs will make your snapshots fail on every run. You either mock everything (cumbersome) or the test becomes useless.
// Bad: timestamp changes every run
expect(generateReport()).toMatchSnapshot();
// Better: mock the timestamp
jest.spyOn(Date, 'now').mockReturnValue(1700000000000);
expect(generateReport()).toMatchSnapshot();When you need to test behavior, not structure. A snapshot tells you the output changed. It doesn't tell you whether the behavior is correct. A login form snapshot doesn't verify that login actually works — a test that fills the form and checks the result does.
A Practical Snapshot Strategy
Use snapshots selectively:
- Keep them small. Snapshot the smallest meaningful unit — a single component, not a full page tree.
- Name them clearly. The test description becomes the snapshot key. Write descriptions that explain what state you're capturing:
renders disabled state,shows error message when email is invalid. - Treat failures seriously. A failing snapshot is a code review question. Before updating, read the diff.
- Complement with behavioral tests. Snapshots protect structure. RTL assertions like
getByRole,getByText, anduserEventprotect behavior. Use both.
// Snapshot: tracks structure
expect(container).toMatchSnapshot();
// Behavioral: verifies it actually works
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
expect(screen.getByText('Saved successfully')).toBeInTheDocument();Organizing Snapshot Files
Jest automatically places .snap files in a __snapshots__ directory next to your test files. Commit these files to version control — they're part of your test suite. When reviewing PRs, treat snapshot diffs with the same scrutiny as code diffs. A snapshot that changed is a component that changed, and that deserves a look.
Snapshot testing isn't a replacement for thoughtful assertions — it's a complement. Used in the right places, it catches regressions you'd otherwise miss. Used indiscriminately, it becomes noise that the team learns to ignore. Know the difference, and snapshots become a genuine asset.