Snapshot Testing

Capture and verify system output against saved snapshots. Covers snapshot testing strategies, serialization, snapshot updates, CI integration, and the patterns that make snapshot tests a reliable safety net for complex output.

Snapshot testing is a technique for validating the structure and content of complex outputs—especially UI components, API responses, and configuration trees—by comparing them against a previously recorded "snapshot" of expected values. It matters most when the output is richly nested, changes frequently, and is difficult to assert over in code: when a component renders a deeply nested object, when a GraphQL response includes nested arrays and optional fields, or when a UI includes dynamic content like tooltips, modals, and conditional layouts.

When to Use Snapshot Testing

Use snapshot testing when the output under test is complex, multi-layered, and likely to evolve. It is ideal for:

It shines when assertions across multiple levels are tedious or brittle: a test that checks expect(component).toHaveProp('title', 'Hello') and expect(component).toHaveClass('active') and expect(component).toHaveChildCount(3) becomes unwieldy when the component renders dozens of nested elements. Snapshot testing captures all these details in a single, stable artifact.

Writing a Snapshot Test

To write a snapshot test, capture the output of a test subject and save it as a file. The test framework reads the previous snapshot, serializes the actual output, and compares it to the expected version. If they differ, the test fails and the new snapshot is written.

Basic Example: A UI Component

Given a React component that renders a user profile:

function UserProfile({ user, showBio = true }) {
  return (
    <div className="profile">
      <h1>{user.name}</h1>
      <p className="email">{user.email}</p>
      {showBio && <p className="bio">{user.bio}</p>}
      <ul className="tags">
        {user.tags.map(tag => (
          <li key={tag.id}>{tag.name}</li>
        ))}
      </ul>
    </div>
  );
}

A snapshot test using Jest (the de facto standard for snapshot testing) looks like:

import { render } from '@testing-library/react';
import UserProfile from './UserProfile';

test('renders user profile', () => {
  const user = {
    name: 'Alice Smith',
    email: '[email protected]',
    bio: 'Frontend engineer and coffee enthusiast.',
    tags: [
      { id: 1, name: 'React' },
      { id: 2, name: 'TypeScript' },
      { id: 3, name: 'Testing' },
    ],
  };

  const { container } = render(<UserProfile user={user} showBio={true} />);

  expect(container).toMatchInlineSnapshot(`
    <div
      class="profile"
    >
      <h1>
        Alice Smith
      </h1>
      <p
        class="email"
      >
        [email protected]
      </p>
      <p
        class="bio"
      >
        Frontend engineer and coffee enthusiast.
      </p>
      <ul
        class="tags"
      >
        <li>
          React
        </li>
        <li>
          TypeScript
        </li>
        <li>
          Testing
        </li>
      </ul>
    </div>
  `);
});

The toMatchInlineSnapshot matcher serializes the container element (an HTML Element) and compares it against the expected HTML. The snapshot is embedded directly in the test file.

Managing Snapshot Files

Snapshot files are stored alongside test files, typically in a __snapshots__ directory or as inline snapshots. Each test generates a .snap file with the same name as the test file.

For a test file UserProfile.test.js, the associated snapshot is UserProfile.test.js.snap:

// UserProfile.test.js.snap
exports[`renders user profile 1`] = `...`;

The snapshot content is a stringified, canonical representation of the output. Common formats include:

Snapshot File Format

The snapshot file uses a key-value format with test case names as keys:

exports[`renders user profile 1`] = `
<div class="profile">
  <h1>Alice Smith</h1>
  <p class="email">[email protected]</p>
  <p class="bio">Frontend engineer and coffee enthusiast.</p>
  <ul class="tags">
    <li>React</li>
    <li>TypeScript</li>
    <li>Testing</li>
  </ul>
</div>
`;

exports[`renders user profile 2`] = `
<div class="profile">
  <h1>Bob Johnson</h1>
  <p class="email">[email protected]</p>
  <ul class="tags">
    <li>Node.js</li>
    <li>GraphQL</li>
  </ul>
</div>
`;

The keys are generated from the test name, and the values are the serialized outputs.

Running and Updating Snapshots

Use the test runner to generate and update snapshots. In Jest, the command is:

npx jest

To update all snapshots, run:

npx jest --updateSnapshot

npx jest -u

This command:

Updating Snapshots in CI

In CI, always run with --updateSnapshot during a snapshot release phase:

- name: Run tests with snapshot updates
  run: |
    npm install
    npx jest --updateSnapshot

When a snapshot fails in CI, the test runner reports the differences between actual and expected values. The output includes:

Snapshot Diffing and Failure Modes

Snapshot tests fail when the actual output differs from the stored snapshot. The diff is usually line-by-line, showing the expected and actual values side-by-side.

Example Failure Output

Expected:
  <div class="profile">
    <h1>Alice Smith</h1>
    <p class="email">[email protected]</p>
    <p class="bio">Frontend engineer and coffee enthusiast.</p>
    <ul class="tags">
      <li>React</li>
      <li>TypeScript</li>
      <li>Testing</li>
    </ul>
  </div>

Received:
  <div class="profile">
    <h1>Alice Smith</h1>
    <p class="email">[email protected]</p>
    <p class="bio">Frontend engineer and coffee lover.</p>  <!-- Changed: "enthusiast" → "lover" -->
    <ul class="tags">
      <li>React</li>
      <li>TypeScript</li>
      <li>Testing</li>
      <li>Testing Tools</li>  <!-- Added: new tag -->
    </ul>
  </div>

The diff highlights:

Silent Failures in Snapshot Testing

expect(JSON.stringify(data, Object.keys(data).sort())).toMatchSnapshot();

Advanced Snapshot Patterns

Conditional Snapshot Updates

Update snapshots conditionally based on environment or input:

if (process.env.NODE_ENV === 'development') {
  test('renders user profile', () => {
    // ... test logic
    expect(container).toMatchInlineSnapshot();
  });
} else {
  test('renders user profile', () => {
    // ... test logic
    expect(container).toMatchSnapshot();
  });
}

In development, use inline snapshots for quick feedback. In production, use named snapshots for better diffing.

Snapshot with Custom Serializers

For complex data types (e.g., Date, Map, Set, Moment), define a custom serializer that controls how values are rendered.

// customSerializer.js
const { print } = require('jest-serializer');

const serialize = (value, config, indentation, depth, refs) => {
  const serialized = {
    ...value,
    createdAt: value.createdAt.toISOString(),
    tags: Array.from(value.tags),
  };

  return print(serialized, config, indentation, depth, refs);
};

module.exports = {
  serialize,
  test: (val) => val instanceof User,
};

Register it in jest.config.js:

module.exports = {
  snapshotSerializers: ['<rootDir>/customSerializer.js'],
};

Snapshot Testing API Responses

For API testing, serialize the entire response body and compare it against a snapshot.

Given an API endpoint returning:

{
  "data": {
    "user": {
      "id": 123,
      "name": "Alice Smith",
      "email": "[email protected]",
      "settings": {
        "theme": "dark",
        "notifications": true
      },
      "recentPosts": [
        {
          "id": 1,
          "title": "Hello World",
          "publishedAt": "2023-10-05T12:00:00Z"
        }
      ]
    }
  }
}

A snapshot test using jest and supertest:

import request from 'supertest';
import app from '../app';

test('GET /api/user/123 returns user with settings', async () => {
  const res = await request(app).get('/api/user/123');

  expect(res.body).toMatchSnapshot();
});

The toMatchSnapshot() matcher serializes the res.body to JSON and compares it against the expected snapshot.

Best Practices and Gotchas

expect(data).toMatchSnapshot({ depth: 3 });
// jest.config.js
module.exports = {
  snapshotFormat: {
    indent: 2,
    width: 80,
    bracketSpacing: true,
    proseWrap: 'preserve',
  },
};

Snapshot testing is not a one-off assertion—it is a living artifact. It evolves with the codebase, capturing the state of complex systems over time. When used well, it reduces the cost of testing, catches regressions early, and becomes a shared language between frontend, backend, and DevOps teams.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.