Snapshot Testing
toMatchSnapshot โ Basic Usage
import { render } from '@testing-library/react';
import { Button } from './Button';
test('Button renders correctly', () => {
const { asFragment } = render(<Button label="Click me" />);
expect(asFragment()).toMatchSnapshot();
});
// For plain objects / values
test('transforms user data', () => {
const result = transformUser({ id: 1, name: 'Alice', role: 'admin' });
expect(result).toMatchSnapshot();
});
// Snapshot file is saved at __snapshots__/Button.test.tsx.snap
// Example .snap content:
// exports[`Button renders correctly 1`] = `
// <DocumentFragment>
// <button class="btn btn-primary">
// Click me
// </button>
// </DocumentFragment>
// `;
Inline Snapshots
// Jest writes the snapshot value inline on first run
test('formats currency', () => {
expect(formatCurrency(1234.5, 'USD')).toMatchInlineSnapshot(`"$1,234.50"`);
});
test('error object shape', () => {
const err = createError('NOT_FOUND', 'User not found');
expect(err).toMatchInlineSnapshot(`
Object {
"code": "NOT_FOUND",
"message": "User not found",
"timestamp": Any<String>,
}
`);
});
// Benefit: snapshot lives with the test โ easier to review in PRs
// Use expect.any() for dynamic values
expect({
id: expect.any(Number),
createdAt: expect.any(String),
name: 'Alice',
}).toMatchInlineSnapshot();
Updating Snapshots
# Update all obsolete/changed snapshots
npx jest --updateSnapshot
npx jest -u
# Update snapshots for a specific test file
npx jest Button.test.tsx --updateSnapshot
# Interactive snapshot update mode
npx jest --watch
# then press 'u' to update failing snapshots
# Delete obsolete snapshots (no longer referenced)
npx jest --ci # fails on new/changed snapshots in CI
# jest.config.js โ warn on obsolete snapshots
module.exports = {
snapshotFormat: {
escapeString: false,
printBasicPrototype: false,
},
};
Custom Snapshot Serializers
// Add a custom serializer for a special object type
expect.addSnapshotSerializer({
test: (val) => val && val.__type === 'Color',
print: (val) => `Color(${val.r}, ${val.g}, ${val.b})`,
});
test('color object', () => {
expect({ __type: 'Color', r: 255, g: 0, b: 128 })
.toMatchInlineSnapshot(`Color(255, 0, 128)`);
});
// jest.config.js โ global serializers
module.exports = {
snapshotSerializers: [
'enzyme-to-json/serializer',
'jest-serializer-html',
'./src/test-utils/mySerializer.js',
],
};
// jest-styled-components serializer (popular with styled-components)
import 'jest-styled-components';
test('styled button snapshot', () => {
const tree = renderer.create(<StyledButton />).toJSON();
expect(tree).toMatchSnapshot(); // includes CSS in snapshot
});
Async Components & Dynamic Values
import { render, screen, waitFor } from '@testing-library/react';
test('async data loads into component', async () => {
const { asFragment } = render(<UserProfile userId="1" />);
await waitFor(() => screen.getByText('Alice'));
expect(asFragment()).toMatchSnapshot();
});
// Masking dynamic values with property matchers
test('API response shape', () => {
const response = { id: 123, createdAt: new Date().toISOString(), name: 'Bob' };
expect(response).toMatchSnapshot({
id: expect.any(Number),
createdAt: expect.any(String),
});
});
// Snapshot of serialized JSON
test('serializes config', () => {
const config = buildConfig({ env: 'test', debug: false });
expect(JSON.stringify(config, null, 2)).toMatchSnapshot();
});
Snapshot Best Practices
| Practice | Why |
|---|---|
| Commit .snap files to git | Snapshots are part of your test suite |
| Review snapshot diffs in PRs | Catch unintentional UI regressions |
| Keep snapshots small & focused | Large snapshots are hard to review |
| Use inline for simple values | Keeps context in one file |
| Use property matchers for IDs/timestamps | Avoid flaky tests from dynamic data |
| Avoid snapshots for logic tests | Use explicit assertions for business logic |