# Snapshots (/docs/test/snapshots)

<!-- agent-signals: reading_time_min: 6 · est_tokens: 2435 · updated: 2026-07-28 -->
Related: [Writing tests](/docs/test/writing-tests.md), [Test configuration](/docs/test/configuration.md), [Runtime behavior](/docs/test/runtime-behavior.md), [Finding tests](/docs/test/discovery.md), [Lifecycle hooks](/docs/test/lifecycle.md), [Mocks](/docs/test/mocks.md)

Snapshot testing saves the output of a value and compares it against future test runs. Use it for UI components, complex objects, or any output that needs to remain consistent.

## Basic Snapshots [#basic-snapshots]

Snapshot tests are written using the `.toMatchSnapshot()` matcher:

```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

test("snap", () => {
  expect("foo").toMatchSnapshot();
});
```

The first time this test runs, Bun serializes the argument to `expect` and writes it to a snapshot file in a `__snapshots__` directory alongside the test file.

### Snapshot Files [#snapshot-files]

After the first run, Bun creates:

```text title="directory structure" icon="file-directory"
your-project/
├── snap.test.ts
└── __snapshots__/
    └── snap.test.ts.snap
```

The snapshot file contains:

```ts title="__snapshots__/snap.test.ts.snap" icon="file-code"
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots

exports[`snap 1`] = `"foo"`;
```

On future runs, Bun compares the argument against the snapshot on disk.

## Updating Snapshots [#updating-snapshots]

Regenerate snapshots with:

```bash terminal icon="terminal"
bun test --update-snapshots
```

Do this when you've intentionally changed the output or added new snapshot tests.

## Inline Snapshots [#inline-snapshots]

For smaller values, use `.toMatchInlineSnapshot()`. Inline snapshots are stored directly in your test file:

```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

test("inline snapshot", () => {
  // First run: snapshot will be inserted automatically
  expect({ hello: "world" }).toMatchInlineSnapshot();
});
```

After the first run, Bun automatically updates your test file:

```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

test("inline snapshot", () => {
  expect({ hello: "world" }).toMatchInlineSnapshot(`
{
  "hello": "world",
}
`);
});
```

### Using Inline Snapshots [#using-inline-snapshots]

1. Write your test with `.toMatchInlineSnapshot()`
2. Run the test once
3. Bun automatically updates your test file with the snapshot
4. On subsequent runs, Bun compares the value against the inline snapshot

## Error Snapshots [#error-snapshots]

You can also snapshot error messages with `.toThrowErrorMatchingSnapshot()` and `.toThrowErrorMatchingInlineSnapshot()`:

```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

test("error snapshot", () => {
  expect(() => {
    throw new Error("Something went wrong");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("Another error");
  }).toThrowErrorMatchingInlineSnapshot();
});
```

After running, the inline version becomes:

```ts title="test.ts" icon="/icons/typescript.svg"
test("error snapshot", () => {
  expect(() => {
    throw new Error("Something went wrong");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("Another error");
  }).toThrowErrorMatchingInlineSnapshot(`"Another error"`);
});
```

## Advanced Snapshot Usage [#advanced-snapshot-usage]

### Complex Objects [#complex-objects]

Snapshots work well with complex nested objects:

```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

test("complex object snapshot", () => {
  const user = {
    id: 1,
    name: "John Doe",
    email: "john@example.com",
    profile: {
      age: 30,
      preferences: {
        theme: "dark",
        notifications: true,
      },
    },
    tags: ["developer", "javascript", "bun"],
  };

  expect(user).toMatchSnapshot();
});
```

### Array Snapshots [#array-snapshots]

Arrays are also well-suited for snapshot testing:

```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

test("array snapshot", () => {
  const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
  expect(numbers).toMatchSnapshot();
});
```

### Function Output Snapshots [#function-output-snapshots]

Snapshot the output of functions:

```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

function generateReport(data: any[]) {
  return {
    total: data.length,
    summary: data.map(item => ({ id: item.id, name: item.name })),
    timestamp: "2024-01-01", // Fixed for testing
  };
}

test("report generation", () => {
  const data = [
    { id: 1, name: "Alice", age: 30 },
    { id: 2, name: "Bob", age: 25 },
  ];

  expect(generateReport(data)).toMatchSnapshot();
});
```

## React Component Snapshots [#react-component-snapshots]

Snapshots work well for React components:

```tsx title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";

function Button({ children, variant = "primary" }) {
  return <button className={`btn btn-${variant}`}>{children}</button>;
}

test("Button component snapshots", () => {
  const { container: primary } = render(<Button>Click me</Button>);
  const { container: secondary } = render(<Button variant="secondary">Cancel</Button>);

  expect(primary.innerHTML).toMatchSnapshot();
  expect(secondary.innerHTML).toMatchSnapshot();
});
```

## Property Matchers [#property-matchers]

For values that change between test runs (like timestamps or IDs), use property matchers:

```ts title="test.ts" icon="/icons/typescript.svg"
import { test, expect } from "bun:test";

test("snapshot with dynamic values", () => {
  const user = {
    id: Math.random(), // This changes every run
    name: "John",
    createdAt: new Date().toISOString(), // This also changes
  };

  expect(user).toMatchSnapshot({
    id: expect.any(Number),
    createdAt: expect.any(String),
  });
});
```

The snapshot file stores:

```txt title="snapshot file" icon="file-code"
exports[`snapshot with dynamic values 1`] = `
{
  "createdAt": Any<String>,
  "id": Any<Number>,
  "name": "John",
}
`;
```

## Best Practices [#best-practices]

### Keep Snapshots Small [#keep-snapshots-small]

```ts title="test.ts" icon="/icons/typescript.svg"
// Good: Focused snapshots
test("user name formatting", () => {
  const formatted = formatUserName("john", "doe");
  expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});

// Avoid: Huge snapshots that are hard to review
test("entire page render", () => {
  const page = renderEntirePage();
  expect(page).toMatchSnapshot(); // This could be thousands of lines
});
```

### Use Descriptive Test Names [#use-descriptive-test-names]

```ts title="test.ts" icon="/icons/typescript.svg"
// Good: Clear what the snapshot represents
test("formats currency with USD symbol", () => {
  expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

// Avoid: Unclear what's being tested
test("format test", () => {
  expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
```

### Group Related Snapshots [#group-related-snapshots]

```ts title="test.ts" icon="/icons/typescript.svg"
import { describe, test, expect } from "bun:test";

describe("Button component", () => {
  test("primary variant", () => {
    expect(render(<Button variant="primary">Click</Button>))
      .toMatchSnapshot();
  });

  test("secondary variant", () => {
    expect(render(<Button variant="secondary">Cancel</Button>))
      .toMatchSnapshot();
  });

  test("disabled state", () => {
    expect(render(<Button disabled>Disabled</Button>))
      .toMatchSnapshot();
  });
});
```

### Handle Dynamic Data [#handle-dynamic-data]

```ts title="test.ts" icon="/icons/typescript.svg"
// Good: Normalize dynamic data
test("API response format", () => {
  const response = {
    data: { id: 1, name: "Test" },
    timestamp: Date.now(),
    requestId: generateId(),
  };

  expect({
    ...response,
    timestamp: "TIMESTAMP",
    requestId: "REQUEST_ID",
  }).toMatchSnapshot();
});

// Or use property matchers
test("API response with matchers", () => {
  const response = getApiResponse();

  expect(response).toMatchSnapshot({
    timestamp: expect.any(Number),
    requestId: expect.any(String),
  });
});
```

## Managing Snapshots [#managing-snapshots]

### Reviewing Snapshot Changes [#reviewing-snapshot-changes]

When snapshots change, carefully review them:

```bash terminal icon="terminal"
# See what changed
git diff __snapshots__/

# Update if changes are intentional
bun test --update-snapshots

# Commit the updated snapshots
git add __snapshots__/
git commit -m "Update snapshots after UI changes"
```

### Organizing Large Snapshot Files [#organizing-large-snapshot-files]

For large projects, consider organizing tests to keep snapshot files manageable:

```text title="directory structure" icon="file-directory"
tests/
├── components/
│   ├── Button.test.tsx
│   └── __snapshots__/
│       └── Button.test.tsx.snap
├── utils/
│   ├── formatters.test.ts
│   └── __snapshots__/
│       └── formatters.test.ts.snap
```

## Troubleshooting [#troubleshooting]

### Snapshot Failures [#snapshot-failures]

When snapshots fail, you'll see a diff:

```diff title="diff" icon="file-code"
- Expected
+ Received

  Object {
-   "name": "John",
+   "name": "Jane",
  }
```

Common causes:

* Intentional changes (update with `--update-snapshots`)
* Unintentional changes (fix the code)
* Dynamic data (use property matchers)
* Environment differences (normalize the data)

### Platform Differences [#platform-differences]

Be aware of platform-specific differences:

```ts title="test.ts" icon="/icons/typescript.svg"
// Paths might differ between Windows/Unix
test("file operations", () => {
  const result = processFile("./test.txt");

  expect({
    ...result,
    path: result.path.replace(/\\/g, "/"), // Normalize paths
  }).toMatchSnapshot();
});
```
