Skip to content

Snapshot Testing

A snapshot test renders a value once, saves it as a golden file, and on every later run compares the new output against the stored snapshot. If they differ, the test fails and you either accept the change (updating the snapshot) or fix the bug.

Snapshots are ideal when the output is verbose or structural and writing an explicit assertion by hand would be tedious: serialised objects, rendered HTML, CLI help text.

first run later runs
───────── ──────────
render output render output
│ │
▼ ▼
write __snapshots__ compare to stored
.snap file │
├── equal → pass
└── differ → fail → review → update?

Inline snapshots store the expected value next to the test, so the expectation is reviewed like normal code instead of hidden in a .snap file.

Use it for

  • Large structural output: serialised config, ASTs, CLI --help, error messages
  • Rendered HTML or component trees where a full-tree diff is useful
  • Detecting unintended change in stable, human-reviewed artefacts
  • Bootstrapping tests for legacy output you cannot yet assert precisely

Avoid it when

  • The value is small — write an explicit expect(x).toBe(...) instead
  • The output is inherently unstable (timestamps, random ids, absolute paths)
  • The snapshot is accepted blindly on every failure — it then tests nothing
  • It becomes a huge opaque blob no reviewer reads
Tool Ecosystem Notes
Vitest snapshots JS/TS toMatchSnapshot, toMatchInlineSnapshot, -u to update
Jest snapshots JS/TS The original; same API as Vitest
insta Rust assert_snapshot!/assert_debug_snapshot!, cargo insta review
Insta / ApprovalTests .NET Verify library for approval-style snapshots
ApprovalTests Multi Golden-master / approval testing across languages
Pytest-regressions Python data_regression, file_regression fixtures
syrupy Python Snapshot plugin for pytest
Playwright snapshots JS/TS toMatchSnapshot for DOM and ARIA trees
Terminal window
npm install -D vitest
npx vitest run # writes missing snapshots
npx vitest run -u # update changed snapshots
import { describe, it, expect } from 'vitest';
import { renderInvoice } from './invoice';
describe('renderInvoice', () => {
it('renders a standard invoice', () => {
expect(renderInvoice(sampleOrder)).toMatchSnapshot();
});
it('formats totals inline', () => {
expect(formatTotal(1234.5)).toMatchInlineSnapshot('"$1,234.50"');
});
});
  • Review every snapshot change in the diff. A snapshot is only as good as the human who reads it; blind -u turns the test into decoration.
  • Keep snapshots small and scoped. Snapshot one meaningful artefact, not a whole page with incidental markup.
  • Make output deterministic. Freeze time, seed randomness, and normalise paths and ids before snapshotting.
  • Prefer inline snapshots for small values. Keeping the expected value in the test makes it reviewable and self-documenting.
  • Commit snapshots to version control. They are test data; if they are gitignored they will churn and hide regressions.
  • Do not snapshot what an assertion says better. expect(x).toBe(42) beats a .snap file containing 42.

Self-assessment quiz

Pick one answer per question, then press Check answers.

  1. How does a snapshot test decide pass or fail?
  2. What is an inline snapshot?
  3. Why is blindly running `-u` on every failure dangerous?
  4. Which output is a poor candidate for snapshot testing?
  5. Which Rust tool provides snapshot testing with a review command?