Snapshot Tests Explained
Learn how snapshot tests work, when they shine, and how to keep them maintainable instead of letting them rot into noise.
What you'll learn
- ✓What a snapshot test is
- ✓When snapshots help and when they hurt
- ✓How to keep snapshots small and meaningful
- ✓Inline vs file snapshots
- ✓Reviewing snapshot diffs in PRs
Prerequisites
- •Comfortable with basic JS or TS testing
What and Why
A snapshot test captures a serialized version of some output, like a component tree or JSON response, and compares it on later runs. If anything changes, the test fails and shows a diff. They are fast to write and cover lots of surface area, but they only earn their keep when teams treat snapshots as code, not noise.
Mental Model
A snapshot test has two phases. First run records the output to disk. Later runs compare the new output against the stored copy. If they match, pass. If not, you either update the snapshot or fix the code.
First run Later runs
--------- ----------
render -> out render -> out
write to disk compare to disk
PASS match -> PASS
differ -> FAIL (show diff)
Update flow: review diff, accept with --updateSnapshot Hands-on Example
A simple component test in Vitest with React.
import { render } from "@testing-library/react";
import { expect, test } from "vitest";
import { PriceTag } from "./PriceTag";
test("renders price tag", () => {
const { container } = render(<PriceTag amount={42.5} currency="USD" />);
expect(container).toMatchSnapshot();
});
The first run creates __snapshots__/PriceTag.test.jsx.snap with the rendered HTML. Later runs compare against it. When you intentionally change the format, run with vitest -u to update.
Inline snapshots keep the expected output next to the test.
test("formats currency", () => {
expect(formatPrice(42.5, "USD")).toMatchInlineSnapshot(`"$42.50"`);
});
For API responses, snapshot a normalized subset rather than the whole payload.
test("user response shape", async () => {
const res = await client.get("/users/1");
const { id, name, role } = res.data;
expect({ id, name, role }).toMatchInlineSnapshot(`
{
"id": 1,
"name": "Ada",
"role": "admin",
}
`);
});
For Python, syrupy does the same job with pytest.
def test_user_shape(client, snapshot):
r = client.get("/users/1")
assert {"id": r.json()["id"], "name": r.json()["name"]} == snapshot
Common Pitfalls
- Treating a snapshot update like a chore. Engineers blindly run
-uand ship regressions. - Snapshotting unstable values: timestamps, randomly ordered keys, generated ids. Strip or freeze them first.
- Massive snapshots that no one reviews. If the file is hundreds of lines, no one will catch a real bug in the diff.
- Using snapshots instead of explicit assertions for important fields.
expect(user.email).toBe(...)is clearer than burying it in a 200-line blob.
Practical Tips
- Always review snapshot diffs in code review. The diff is the test.
- Keep each snapshot focused. One component or one response shape per test.
- Normalize before snapshotting. Replace dates with
<date>and ids with<id>. - Prefer inline snapshots for small values; they document intent in the test file.
- Add serializers for custom types so output stays stable across runs.
Wrap-up
Snapshot tests are powerful when they remain small, focused, and read like documentation. They are a liability when they grow into giant unchecked blobs that everyone updates without thinking. Use them for shapes that are tedious to assert by hand, normalize unstable parts, and review diffs as carefully as you review code.
Related articles
- Testing Vitest Tutorial
A practical guide to Vitest, the testing framework built for modern JavaScript projects. Setup, syntax, mocking, coverage, and how it differs from Jest in ways that matter.
- React React Testing Library Best Practices
Practical guidance for writing maintainable, user-focused tests with React Testing Library, including queries, async patterns, and smart mocking.
- Testing Property-Based Testing: An Introduction
Stop writing one example per test. Property-based testing generates inputs for you and finds the edge cases you would never think to write.
- Testing Contract Tests Explained: Catching Integration Bugs Early
Understand consumer-driven contract testing, how it differs from integration tests, and how tools like Pact prevent breaking API changes between services.