Try the JSON Diff

Snapshot Testing Is Just JSON Diffing With Extra Steps β€” Here's Why That Explains Both Its Strengths and Its Noise

Snapshot testing is, underneath, just "save a JSON diff result and fail the test if it's not empty next time" β€” and seeing it this way explains both why snapshot tests catch bugs that assertion-based tests miss, and why they're notorious for generating noisy, rubber-stamped diffs. Here's how the snapshot-compare-update cycle works, why "everything changed" diffs from one intentional change lead to rubber-stamping, and why a genuine bug can hide among repetitive, expected diff entries.

By sadiqbd Β· June 18, 2026

Share:
Snapshot Testing Is Just JSON Diffing With Extra Steps β€” Here's Why That Explains Both Its Strengths and Its Noise

Snapshot testing β€” a popular technique in modern test suites β€” is, underneath, just "save a JSON diff result and fail the test if it's not empty next time" β€” and understanding it this way explains both why snapshot tests catch real bugs and why they're notorious for generating noisy, rubber-stamped diffs

The previous articles on this site covered structural diff fundamentals, JSON Patch, and practical diff workflows for debugging/migrations. This article addresses snapshot testing β€” a testing methodology built directly on JSON (or JSON-like) structural diffing β€” and why understanding the diffing underneath snapshot tests explains both their value and their most common frustration.


What a snapshot test actually does

A snapshot test works roughly as follows:

  1. Run some code that produces output (a rendered component's structure, an API response, a function's return value β€” anything that can be serialized to JSON or a JSON-like representation)
  2. The first time the test runs, the output is saved as a "snapshot" β€” a stored JSON file representing "what the output looked like at this point"
  3. On subsequent runs, the new output is compared against the saved snapshot β€” using, fundamentally, a structural diff (the same kind of comparison covered in the previous articles)
  4. If the diff is empty (no differences) β€” the test passes: "output is unchanged from the saved snapshot"
  5. If the diff is non-empty β€” the test fails, and the diff is shown to the developer β€” who must decide: is this difference a bug (the code now produces wrong output) β€” or an intentional change (the code correctly now produces different, updated output, and the snapshot should be updated to match)?

Why this is powerful: catching unintended changes anywhere in a large structure

Snapshot testing's core value: it catches any difference, anywhere in a (potentially large, deeply-nested) output structure β€” without requiring the test-writer to have anticipated, in advance, every specific field that might change.

Compare to traditional, "assertion-based" testing: expect(result.user.name).toBe("Alice"), expect(result.user.age).toBe(30), etc. β€” each assertion checks one specific value. If the code changes such that result.user.email unexpectedly changes (a field no assertion was checking) β€” traditional assertion-based tests, checking only name and age, wouldn't notice β€” but a snapshot test, comparing the entire result structure, would flag the email difference β€” because the structural diff covers everything, not just the fields someone thought to assert on.


Why this is frustrating: "everything changed" diffs from small, intentional changes

The flip side of "catches everything": a small, intentional change β€” e.g., adding a new field to every item in a returned array β€” produces a diff showing every item in the array as "changed" (each item now has an additional field it didn't have before) β€” even though, conceptually, this is one simple, intentional change ("we added a new field"), not "N separate changes, one per array item."

For a large array (recall the previous article's discussion of array-diff cascading) β€” this single, intentional change can produce a diff that's pages long β€” every item showing the same, repetitive "+ new field" addition. A developer reviewing this diff β€” recognizing that it's "just" the same intentional change, repeated N times β€” often simply approves/updates the snapshot without carefully reviewing each repetition β€” because reviewing each of N identical repetitions provides no additional information beyond reviewing the first one.

This "rubber-stamping" β€” approving a large, repetitive diff based on recognizing the pattern, without line-by-line review of every occurrence β€” is, in itself, reasonable for truly repetitive, pattern-consistent changes β€” **but it also means: if one of the N array items, amid the otherwise-repetitive diff, contains a DIFFERENT, unintended change (a genuine bug, hiding among the expected, repetitive additions) β€” a developer rubber-stamping the overall "looks like the same pattern, N times" diff might miss that one, different, buried change.


"Snapshot update" workflows: the diff becomes the new baseline

When a snapshot test fails, and the developer determines the new output is correct (an intentional change) β€” the snapshot is updated to match the new output β€” typically via a command/flag that overwrites the stored snapshot file with the current output.

This means: the diff, once "approved," becomes the new "no diff" baseline β€” future test runs compare against this updated snapshot β€” if the code later changes again β€” the new diff is against this updated baseline, not the original.

A practical implication: snapshot updates should be reviewed as part of code review (the updated snapshot file, itself, is typically a file in version control β€” its changes appear in diffs/pull-requests, just like code changes) β€” **a pull request that includes snapshot updates is, effectively, saying "here's the (JSON) diff of what changed in this output, as a result of my code changes β€” please review whether THIS diff represents intended behavior."

If snapshot updates are committed without this review (e.g., a developer runs "update all snapshots" to make failing tests pass, without examining what changed, and commits the result) β€” the snapshot tests no longer provide meaningful protection β€” they'll "pass" because they're now comparing against whatever the (possibly buggy) current output is β€” the test suite reports "green," while the underlying output might have genuinely, unintentionally regressed.


Reducing diff noise: "inline snapshots" and "property matchers"

Some testing frameworks offer techniques to reduce the "everything changed" noise problem:

Excluding volatile fields: if a structure includes fields that are expected to differ on every run (timestamps, randomly-generated IDs, etc.) β€” these fields can be excluded from the snapshot comparison (or replaced with placeholder values before comparison) β€” avoiding the snapshot test failing on every run simply because "the timestamp is different this time," which, while technically "a diff," isn't meaningful β€” related, conceptually, to "Vary"-style exclusions β€” deciding, upfront, "these specific fields aren't part of what we're checking."

Smaller, more focused snapshots: rather than one enormous snapshot covering an entire, complex output β€” multiple, smaller snapshots, each covering a more focused portion of the output β€” reduces the "everything changed" cascade for any given, localized change β€” though at the cost of needing more individual snapshot files/assertions to cover the same overall surface.


How to use the JSON Diff tool on sadiqbd.com

  1. For reviewing snapshot-test failures: paste the old (saved snapshot) and new (current output) JSON β€” the structural diff output is, effectively, what a snapshot-testing framework shows you, just in this tool's presentation rather than the testing framework's
  2. For large, repetitive diffs (the "every array item changed" pattern): use the diff to confirm the pattern is, indeed, consistent across all items β€” and specifically check whether any individual item deviates from the otherwise-repetitive pattern β€” this manual "scan for the outlier" check is precisely what "rubber-stamping" skips, and what catching bugs-hidden-in-repetitive-diffs requires
  3. Before updating a snapshot: review the diff (via this tool, or the testing framework's own diff display) as carefully as you would review any other code change β€” snapshot updates are code changes, and deserve the same review attention

Frequently Asked Questions

Is snapshot testing a "replacement" for traditional, assertion-based tests, or a complement? Generally considered a complement, not a replacement β€” snapshot tests excel at "catch any unintended change, anywhere in a complex structure" β€” but, as discussed, can generate noisy diffs for intentional changes, and rely on developers carefully reviewing updates. Traditional assertions (expect(x).toBe(y)) remain valuable for specific, important invariants that should be checked explicitly, with clear, targeted failure messages β€” regardless of whether the broader output structure happens to match a snapshot. Many test suites use both: snapshot tests for "broad, catch-anything" coverage of complex outputs, plus targeted assertions for specific, critical properties that warrant explicit, named checks regardless of snapshot status.

Is the JSON Diff tool free? Yes β€” completely free, no sign-up required.

Try the JSON Diff tool free at sadiqbd.com β€” compare JSON snapshots and review structural differences for testing and debugging.

Share:
Try the related tool:
Open JSON Diff

More JSON Diff articles