Capture and verify system output against saved snapshots. Covers snapshot testing strategies, serialization, snapshot updates, CI integration, and the patterns that make snapshot tests a reliable safety net for complex output.
Snapshot testing is a technique for validating the structure and content of complex outputs—especially UI components, API responses, and configuration trees—by comparing them against a previously recorded "snapshot" of expected values. It matters most when the output is richly nested, changes frequently, and is difficult to assert over in code: when a component renders a deeply nested object, when a GraphQL response includes nested arrays and optional fields, or when a UI includes dynamic content like tooltips, modals, and conditional layouts.
Use snapshot testing when the output under test is complex, multi-layered, and likely to evolve. It is ideal for:
It shines when assertions across multiple levels are tedious or brittle: a test that checks expect(component).toHaveProp('title', 'Hello') and expect(component).toHaveClass('active') and expect(component).toHaveChildCount(3) becomes unwieldy when the component renders dozens of nested elements. Snapshot testing captures all these details in a single, stable artifact.
To write a snapshot test, capture the output of a test subject and save it as a file. The test framework reads the previous snapshot, serializes the actual output, and compares it to the expected version. If they differ, the test fails and the new snapshot is written.
Given a React component that renders a user profile:
function UserProfile({ user, showBio = true }) {
return (
<div className="profile">
<h1>{user.name}</h1>
<p className="email">{user.email}</p>
{showBio && <p className="bio">{user.bio}</p>}
<ul className="tags">
{user.tags.map(tag => (
<li key={tag.id}>{tag.name}</li>
))}
</ul>
</div>
);
}
A snapshot test using Jest (the de facto standard for snapshot testing) looks like:
import { render } from '@testing-library/react';
import UserProfile from './UserProfile';
test('renders user profile', () => {
const user = {
name: 'Alice Smith',
email: '[email protected]',
bio: 'Frontend engineer and coffee enthusiast.',
tags: [
{ id: 1, name: 'React' },
{ id: 2, name: 'TypeScript' },
{ id: 3, name: 'Testing' },
],
};
const { container } = render(<UserProfile user={user} showBio={true} />);
expect(container).toMatchInlineSnapshot(`
<div
class="profile"
>
<h1>
Alice Smith
</h1>
<p
class="email"
>
[email protected]
</p>
<p
class="bio"
>
Frontend engineer and coffee enthusiast.
</p>
<ul
class="tags"
>
<li>
React
</li>
<li>
TypeScript
</li>
<li>
Testing
</li>
</ul>
</div>
`);
});
The toMatchInlineSnapshot matcher serializes the container element (an HTML Element) and compares it against the expected HTML. The snapshot is embedded directly in the test file.
Snapshot files are stored alongside test files, typically in a __snapshots__ directory or as inline snapshots. Each test generates a .snap file with the same name as the test file.
For a test file UserProfile.test.js, the associated snapshot is UserProfile.test.js.snap:
// UserProfile.test.js.snap
exports[`renders user profile 1`] = `...`;
The snapshot content is a stringified, canonical representation of the output. Common formats include:
The snapshot file uses a key-value format with test case names as keys:
exports[`renders user profile 1`] = `
<div class="profile">
<h1>Alice Smith</h1>
<p class="email">[email protected]</p>
<p class="bio">Frontend engineer and coffee enthusiast.</p>
<ul class="tags">
<li>React</li>
<li>TypeScript</li>
<li>Testing</li>
</ul>
</div>
`;
exports[`renders user profile 2`] = `
<div class="profile">
<h1>Bob Johnson</h1>
<p class="email">[email protected]</p>
<ul class="tags">
<li>Node.js</li>
<li>GraphQL</li>
</ul>
</div>
`;
The keys are generated from the test name, and the values are the serialized outputs.
Use the test runner to generate and update snapshots. In Jest, the command is:
npx jest
To update all snapshots, run:
npx jest --updateSnapshot
npx jest -u
This command:
In CI, always run with --updateSnapshot during a snapshot release phase:
- name: Run tests with snapshot updates
run: |
npm install
npx jest --updateSnapshot
When a snapshot fails in CI, the test runner reports the differences between actual and expected values. The output includes:
Snapshot tests fail when the actual output differs from the stored snapshot. The diff is usually line-by-line, showing the expected and actual values side-by-side.
Expected:
<div class="profile">
<h1>Alice Smith</h1>
<p class="email">[email protected]</p>
<p class="bio">Frontend engineer and coffee enthusiast.</p>
<ul class="tags">
<li>React</li>
<li>TypeScript</li>
<li>Testing</li>
</ul>
</div>
Received:
<div class="profile">
<h1>Alice Smith</h1>
<p class="email">[email protected]</p>
<p class="bio">Frontend engineer and coffee lover.</p> <!-- Changed: "enthusiast" → "lover" -->
<ul class="tags">
<li>React</li>
<li>TypeScript</li>
<li>Testing</li>
<li>Testing Tools</li> <!-- Added: new tag -->
</ul>
</div>
The diff highlights:
enthusiast → lover).<li>Testing Tools</li>).<p> element that no longer exists).--updateSnapshot periodically to catch this.--onlyChanged to update only snapshots for changed test files.Object.entries or Map to build a response, but the snapshot expects a specific key order, a change in iteration order causes silent failures. Use JSON.stringify with a stable order:expect(JSON.stringify(data, Object.keys(data).sort())).toMatchSnapshot();
Update snapshots conditionally based on environment or input:
if (process.env.NODE_ENV === 'development') {
test('renders user profile', () => {
// ... test logic
expect(container).toMatchInlineSnapshot();
});
} else {
test('renders user profile', () => {
// ... test logic
expect(container).toMatchSnapshot();
});
}
In development, use inline snapshots for quick feedback. In production, use named snapshots for better diffing.
For complex data types (e.g., Date, Map, Set, Moment), define a custom serializer that controls how values are rendered.
// customSerializer.js
const { print } = require('jest-serializer');
const serialize = (value, config, indentation, depth, refs) => {
const serialized = {
...value,
createdAt: value.createdAt.toISOString(),
tags: Array.from(value.tags),
};
return print(serialized, config, indentation, depth, refs);
};
module.exports = {
serialize,
test: (val) => val instanceof User,
};
Register it in jest.config.js:
module.exports = {
snapshotSerializers: ['<rootDir>/customSerializer.js'],
};
For API testing, serialize the entire response body and compare it against a snapshot.
Given an API endpoint returning:
{
"data": {
"user": {
"id": 123,
"name": "Alice Smith",
"email": "[email protected]",
"settings": {
"theme": "dark",
"notifications": true
},
"recentPosts": [
{
"id": 1,
"title": "Hello World",
"publishedAt": "2023-10-05T12:00:00Z"
}
]
}
}
}
A snapshot test using jest and supertest:
import request from 'supertest';
import app from '../app';
test('GET /api/user/123 returns user with settings', async () => {
const res = await request(app).get('/api/user/123');
expect(res.body).toMatchSnapshot();
});
The toMatchSnapshot() matcher serializes the res.body to JSON and compares it against the expected snapshot.
renders user profile with bio and tags instead of renders user profile.toMatchInlineSnapshot() for small, stable outputs: when the output is not likely to change, embedding it in the test file reduces cognitive load.container.querySelector('.profile') to isolate the relevant part.toMatchSnapshot() matcher that includes a depth option:expect(data).toMatchSnapshot({ depth: 3 });
jest’s snapshotFormat option to control how snapshots are formatted:// jest.config.js
module.exports = {
snapshotFormat: {
indent: 2,
width: 80,
bracketSpacing: true,
proseWrap: 'preserve',
},
};
--updateSnapshot before merging PRs. CI should fail if snapshots differ from the main branch.Snapshot testing is not a one-off assertion—it is a living artifact. It evolves with the codebase, capturing the state of complex systems over time. When used well, it reduces the cost of testing, catches regressions early, and becomes a shared language between frontend, backend, and DevOps teams.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy