jsontestingjestjavascript

JSON Snapshot Testing: Jest Fixtures & Golden Files Done Right

·8 min read·Tool Guides

Testing "did the output change" instead of "is the output right"

Writing a full assertion for a function that returns a 40-field JSON object is tedious and brittle — you'd either check three fields and miss regressions in the other 37, or write forty expect() lines that need updating every time the shape evolves. Snapshot testing takes a different approach: capture the actual JSON output once, save it as a fixture ("golden file"), and on every future test run, compare the new output against the saved one — any difference fails the test and shows you exactly what changed.

How a Jest snapshot actually works

javascript
test("formats a user profile", () => {
  const result = formatUserProfile({ id: 1, name: "Ada", roles: ["admin"] });
  expect(result).toMatchSnapshot();
});

The first run has nothing to compare against, so Jest writes the current output to a __snapshots__/*.snap file and passes automatically:

text
// __snapshots__/user.test.js.snap
exports[`formats a user profile 1`] = `
{
  "id": 1,
  "name": "Ada",
  "roles": ["admin"],
  "displayName": "Ada (admin)"
}
`;

Every subsequent run compares the new output against this saved snapshot. If they match, the test passes silently. If they differ, Jest fails the test and prints a colored diff of exactly what changed — which is the entire value proposition: you get full-object regression detection without hand-writing a single field-level assertion.

The discipline that makes or breaks snapshot testing

Snapshot tests have one well-known failure mode: a developer sees a failing snapshot, doesn't understand why, and reflexively runs jest --updateSnapshot to make the red turn green — silently accepting a regression as the new "correct" baseline. The test still passes, but it just certified a bug. The rule that prevents this: every snapshot update must be reviewed like a code change, because a .snap file diff in a pull request is exactly as meaningful as a diff in the code that produced it.

bash
# Update snapshots — but only after confirming the new output is actually correct
npx jest --updateSnapshot

The .snap file diff in your PR is the real regression test here — a reviewer who sees "totalPrice": 49.99 become "totalPrice": 45.99 in a snapshot diff should ask why, exactly as they would for a hand-written assertion change.

Golden files: the same idea outside JavaScript

The concept predates Jest by decades and shows up under the name golden file testing (or "approval testing") in Go, Python, and many other ecosystems — save expected output to a file, compare on every test run:

python
# Python — pytest with a golden JSON file
import json

def test_export_format():
    result = generate_export(sample_order)
    with open("testdata/export_golden.json") as f:
        expected = json.load(f)
    assert result == expected
go
// Go — a common golden-file pattern
func TestExportFormat(t *testing.T) {
    got := GenerateExport(sampleOrder)
    golden, _ := os.ReadFile("testdata/export_golden.json")
    var want map[string]interface{}
    json.Unmarshal(golden, &want)
    if !reflect.DeepEqual(toMap(got), want) {
        t.Errorf("output does not match golden file")
    }
}

Both patterns share Jest's core trade-off: fast to write, powerful at catching *any* unintended change, but only useful if someone actually reviews the diff when a golden file needs updating.

What makes a snapshot brittle instead of useful

Snapshots capturing non-deterministic values — a timestamp, a random ID, a fluctuating computed field — fail on almost every run for reasons that have nothing to do with real bugs, which trains developers to stop trusting (and start blindly approving) snapshot failures. The fix is to normalize those fields before snapshotting, or use a matcher that accepts any value of the right shape:

javascript
test("creates an order with a snapshot-safe timestamp", () => {
  const order = createOrder({ productId: "sku_1" });
  expect(order).toMatchSnapshot({
    id: expect.any(String),
    createdAt: expect.any(String), // don't snapshot the literal timestamp
  });
});

expect.any(String) tells Jest "a string belongs here, but don't compare its exact value" — the snapshot still catches a regression where createdAt becomes a number or disappears entirely, without ever failing just because time passed between test runs.

Snapshot testing API responses specifically

A common, high-value use: snapshot an API endpoint's JSON response shape to catch accidental breaking changes — a renamed field, a type that silently changed from number to string — before they ship to real API consumers:

javascript
test("GET /api/orders/:id returns the expected shape", async () => {
  const response = await request(app).get("/api/orders/ord_1");
  expect(response.body).toMatchSnapshot({
    createdAt: expect.any(String),
    id: expect.any(String),
  });
});

This catches exactly the class of bug that hand-written field assertions tend to miss: an *extra*, unexpected field silently added to the response (a debug field left in, an internal ID accidentally exposed) — a snapshot diff flags it immediately as a new line in the diff, where a targeted expect(body.total).toBe(49.99) assertion would never even look at that field.

Reviewing and maintaining snapshots

  1. Never bulk-approve snapshot updates without reading the diff. Treat a changed .snap file exactly like a changed source file in code review.
  2. Keep snapshots small and focused. A snapshot of one function's output is reviewable; a snapshot of an entire page's rendered HTML tree is not — nobody meaningfully reviews a 2,000-line diff.
  3. Normalize non-deterministic fields (timestamps, UUIDs, random values) with matchers rather than letting them cause perpetual false failures.
  4. Delete obsolete snapshots. Run jest --ci in CI, which fails on obsolete snapshots left behind after a test is removed, keeping the fixture set honest.

To inspect a large .snap file's JSON content directly, paste it into JSONKit's JSON Formatter, or compare an old and new snapshot side by side with the JSON Diff tool when a terminal diff is hard to read.

Frequently asked questions

No — it complements them. Use targeted assertions for behavior you specifically care about (a calculated total is correct), and snapshots for catching *any* unintended structural change across a large or evolving output shape.

A library update can change formatting, field order, or default values in ways that ripple into your output — review the diff to confirm it's an intentional, acceptable change before updating the snapshot, rather than assuming it's safe.

Use property matchers (expect.any(String), expect.any(Number)) for fields that are expected to vary, so the snapshot still validates the field's presence and type without failing on its exact, non-deterministic value.

Yes — .snap files and golden JSON fixtures are part of the test suite and must be versioned so CI and every developer compares against the same baseline.

Try JSONKit — JSON Developer Tools

Format, validate, convert and diff JSON. Instant, accurate and 100% private.