Snapshot Testing
Status: Complete
Category: Testing
Default enforcement: Advisory
Author: PushBackLog team
Tags
- Topic: testing, frontend
- Skillset: frontend, fullstack
- Technology: generic
- Stage: execution, review
Summary
Snapshot testing captures the rendered output or serialised state of a component or function, saves it as a reference file, and then compares subsequent outputs against that reference. Unexpected changes fail the test. It is most useful for detecting unintended regressions in UI components and serialised data structures with many fields. Used carefully, it protects against silent drift; used carelessly, it becomes a collection of assertions that engineers update without reviewing.
Rationale
Preventing unintended visual and structural regressions
Rendering a UI component produces an output with dozens or hundreds of HTML attributes, class names, event bindings, and text nodes. Writing assertions for each individual property is impractical. A snapshot test captures the entire rendered tree once and fails if anything changes unexpectedly — forcing an engineer to acknowledge the change before updating the reference.
The same principle applies to serialised data structures (API responses, event payloads, configuration outputs) with many fields: snapshot testing catches additions, removals, and changes that might otherwise go unnoticed.
The snapshot update trap
The principal risk with snapshot testing is that engineers update snapshots reflexively without reviewing whether the change is correct. When a test says “snapshot changed”, the correct response is to inspect the diff and verify that the change is intentional. The incorrect response is to run --updateSnapshot and commit without looking. This behaviour degrades snapshots from a safety net to a false confidence generator.
Guidance
UI component snapshots (Jest + React Testing Library)
// UserCard.test.tsx
import { render } from '@testing-library/react';
test('UserCard renders a premium user correctly', () => {
const { container } = render(
<UserCard
user={{ name: 'Christy Arthur', plan: 'premium', avatarUrl: null }}
/>
);
expect(container).toMatchSnapshot();
});
First run: creates __snapshots__/UserCard.test.tsx.snap
Subsequent runs: compares against the saved snapshot
Reviewing snapshot diffs
# Update a specific snapshot after confirming the change is intentional
jest -u UserCard.test.tsx
# Never run blindly:
jest -u # updates ALL snapshots without review
When a snapshot diff appears in a PR, the reviewer should inspect the diff file and confirm that every changed line represents intended behaviour.
Inline snapshots for smaller outputs
Inline snapshots live in the test file rather than a separate .snap file:
test('formats a price correctly', () => {
expect(formatPrice(4999, 'GBP')).toMatchInlineSnapshot(`"£49.99"`);
});
Prefer inline snapshots for small, human-readable outputs. Use file snapshots for large rendered trees where the snapshot file is acceptable to maintain.
Serialised data snapshot testing
// Snapshot an API response shape to detect unintended changes
test('GET /orders/:id response shape', async () => {
const response = await request(app).get('/orders/test-order-1');
expect(response.body).toMatchSnapshot();
});
Use this for regression detection, not as a substitute for assertions on business-critical fields.
Dos and don’ts
| Do | Don’t |
|---|---|
| Snapshot stable components with complex output | Snapshot components whose output changes with every render (dates, random IDs) |
| Review snapshot diffs in code review | Update snapshots without reading the diff |
| Use inline snapshots for small outputs | Create enormous snapshots that nobody reads |
| Store snapshot files in version control | Ignore snapshot files or gitignore them |
| Write focused assertions for business-critical properties | Rely solely on snapshots for correctness |
Stabilising dynamic content
Snapshots that include dynamic values (dates, UUIDs, random values) fail on every run. Stabilise them before snapshotting:
// Mock dynamic values to make snapshots stable
jest.useFakeTimers().setSystemTime(new Date('2026-01-01'));
// Or replace dynamic values with deterministic substitutes
const user = {
id: 'test-id-fixed',
createdAt: '2026-01-01T00:00:00.000Z',
name: 'Test User',
};
Best fit for snapshot testing
Snapshot testing is well-suited for:
- UI components with complex, stable HTML output
- API response shape regression detection
- CLI tool output verification
- Compiled/generated output (GraphQL schema, OpenAPI spec, generated types)
It is a poor fit for logic-heavy functions where explicit assertions are more expressive.