Skip to main content
Snapshot testing saves the serialized output of a value and compares it on future runs. This is useful for complex objects, component output, or any value that should remain stable over time.

Basic snapshots

Use .toMatchSnapshot() to capture a value:
import { test, expect } from "bun:test";

test("formats user object", () => {
  const user = { id: 1, name: "Alice", role: "admin" };
  expect(user).toMatchSnapshot();
});
On the first run, the value is serialized and written to a snapshot file. On subsequent runs, the value is compared against the stored snapshot — the test fails if they differ.

Snapshot file location

Snapshots are stored in a __snapshots__ directory alongside the test file:
your-project/
├── user.test.ts
└── __snapshots__/
    └── user.test.ts.snap
The snapshot file contents look like:
__snapshots__/user.test.ts.snap
// Bun Snapshot v1, https://bun.com/docs/test/snapshots

exports[`formats user object 1`] = `
{
  "id": 1,
  "name": "Alice",
  "role": "admin",
}
`;

Updating snapshots

When output intentionally changes, regenerate snapshots with:
bun test --update-snapshots
After updating, commit the snapshot files alongside your code changes so they are reviewed as part of the diff.
git diff __snapshots__/
git add __snapshots__/
git commit -m "update snapshots after API response shape change"

Inline snapshots

For small values, use .toMatchInlineSnapshot() to store the snapshot directly in the test file. On the first run, Bun writes the value into your test file automatically.
import { test, expect } from "bun:test";

test("formats currency", () => {
  expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
After the first run, your test file is updated with the snapshot value filled in. Subsequent runs compare against the inline string.

Error snapshots

Capture and compare error messages with .toThrowErrorMatchingSnapshot() and .toThrowErrorMatchingInlineSnapshot():
import { test, expect } from "bun:test";

test("throws on invalid input", () => {
  expect(() => {
    parseConfig("not valid json");
  }).toThrowErrorMatchingInlineSnapshot(`"Invalid configuration: not valid json"`);
});

Advanced usage

Complex objects

Snapshots are particularly effective for deeply nested structures:
test("generates report", () => {
  const report = generateReport([
    { id: 1, name: "Alice" },
    { id: 2, name: "Bob" },
  ]);

  expect(report).toMatchSnapshot();
});

Property matchers

For values that change between runs (timestamps, random IDs), use property matchers to assert the shape while ignoring volatile values:
test("creates user with dynamic fields", () => {
  const user = {
    id: crypto.randomUUID(),
    name: "Alice",
    createdAt: new Date().toISOString(),
  };

  expect(user).toMatchSnapshot({
    id: expect.any(String),
    createdAt: expect.any(String),
  });
});
The snapshot stores Any<String> for the matched fields while capturing the exact value of name.

React component snapshots

Snapshots work well for testing rendered HTML output:
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";

function Badge({ label, variant = "default" }: { label: string; variant?: string }) {
  return <span className={`badge badge-${variant}`}>{label}</span>;
}

test("Badge renders correctly", () => {
  const { container } = render(<Badge label="New" variant="success" />);
  expect(container.innerHTML).toMatchInlineSnapshot(
    `"<span class=\"badge badge-success\">New</span>"`
  );
});

Custom serializers

Register a custom serializer to control how a type is printed in snapshots:
import { expect } from "bun:test";

expect.addSnapshotSerializer({
  test: (val) => val instanceof Date,
  serialize: (val) => `"${val.toISOString()}"`,
});

test("event with date", () => {
  expect({
    name: "Meeting",
    date: new Date("2024-06-01T10:00:00Z"),
  }).toMatchSnapshot();
});

Troubleshooting snapshot failures

When a snapshot test fails, Bun shows a diff:
- Expected
+ Received

  Object {
-   "name": "Alice",
+   "name": "Bob",
  }
Common causes:
CauseSolution
Intentional changeRun bun test --update-snapshots
Unintentional regressionFix the code that produces the output
Dynamic data in snapshotUse property matchers for volatile fields
Platform-specific formattingNormalize values (e.g., path separators) before snapshotting

Best practices

Snapshot only the part of the output that matters to the test. Large snapshots are hard to review when they change.
// Good: snapshot a specific field
expect(response.body).toMatchInlineSnapshot(`"success"`);

// Avoid: snapshotting an entire page render
expect(renderFullPage()).toMatchSnapshot();
// Good: normalize volatile fields
expect(apiResponse).toMatchSnapshot({
  timestamp: expect.any(Number),
  requestId: expect.any(String),
});

// Avoid: snapshots that break on every run
expect(apiResponse).toMatchSnapshot(); // contains Date.now()
Snapshot diffs in pull requests deserve the same scrutiny as code changes. An unexpected snapshot change is often a sign of a bug.
Organizing snapshots inside describe blocks makes the snapshot file easier to navigate:
describe("Button", () => {
  test("primary variant", () => {
    expect(renderButton("primary")).toMatchSnapshot();
  });

  test("secondary variant", () => {
    expect(renderButton("secondary")).toMatchSnapshot();
  });
});

Build docs developers (and LLMs) love