skills/bun-test-snapshots/SKILL.md
Learn how to use snapshot testing in Bun to save and compare output between test runs
npx skillsauth add jarle/bun-skills Bun SnapshotsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Learn how to use snapshot testing in Bun to save and compare output between test runs
Snapshot testing saves the output of a value and compares it against future test runs. This is particularly useful for UI components, complex objects, or any output that needs to remain consistent.
Snapshot tests are written using the .toMatchSnapshot() matcher:
import { test, expect } from "bun:test";
test("snap", () => {
expect("foo").toMatchSnapshot();
});
The first time this test is run, the argument to expect will be serialized and written to a special snapshot file in a __snapshots__ directory alongside the test file.
After running the test above, Bun will create:
your-project/
├── snap.test.ts
└── __snapshots__/
└── snap.test.ts.snap
The snapshot file contains:
// Bun Snapshot v1, https://bun.com/docs/test/snapshots
exports[`snap 1`] = `"foo"`;
On future runs, the argument is compared against the snapshot on disk.
Snapshots can be re-generated with the following command:
bun test --update-snapshots
This is useful when:
For smaller values, you can use inline snapshots with .toMatchInlineSnapshot(). These snapshots are stored directly in your test file:
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:
import { test, expect } from "bun:test";
test("inline snapshot", () => {
expect({ hello: "world" }).toMatchInlineSnapshot(`
{
"hello": "world",
}
`);
});
.toMatchInlineSnapshot()Inline snapshots are particularly useful for small, simple values where it's helpful to see the expected output right in the test file.
You can also snapshot error messages using .toThrowErrorMatchingSnapshot() and .toThrowErrorMatchingInlineSnapshot():
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:
test("error snapshot", () => {
expect(() => {
throw new Error("Something went wrong");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Another error");
}).toThrowErrorMatchingInlineSnapshot(`"Another error"`);
});
Snapshots work well with complex nested objects:
import { test, expect } from "bun:test";
test("complex object snapshot", () => {
const user = {
id: 1,
name: "John Doe",
email: "[email protected]",
profile: {
age: 30,
preferences: {
theme: "dark",
notifications: true,
},
},
tags: ["developer", "javascript", "bun"],
};
expect(user).toMatchSnapshot();
});
Arrays are also well-suited for snapshot testing:
import { test, expect } from "bun:test";
test("array snapshot", () => {
const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
expect(numbers).toMatchSnapshot();
});
Snapshot the output of functions:
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();
});
Snapshots are particularly useful for React components:
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();
});
For values that change between test runs (like timestamps or IDs), use property matchers:
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 will store:
exports[`snapshot with dynamic values 1`] = `
{
"createdAt": Any<String>,
"id": Any<Number>,
"name": "John",
}
`;
You can customize how objects are serialized in snapshots:
import { test, expect } from "bun:test";
// Custom serializer for Date objects
expect.addSnapshotSerializer({
test: val => val instanceof Date,
serialize: val => `"${val.toISOString()}"`,
});
test("custom serializer", () => {
const event = {
name: "Meeting",
date: new Date("2024-01-01T10:00:00Z"),
};
expect(event).toMatchSnapshot();
});
// 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
});
// 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"`);
});
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();
});
});
// 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),
});
});
When snapshots change, carefully review them:
# 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"
Bun will warn about unused snapshots:
Warning: 1 unused snapshot found:
my-test.test.ts.snap: "old test that no longer exists 1"
Remove unused snapshots by deleting them from the snapshot files or by running tests with cleanup flags if available.
For large projects, consider organizing tests to keep snapshot files manageable:
tests/
├── components/
│ ├── Button.test.tsx
│ └── __snapshots__/
│ └── Button.test.tsx.snap
├── utils/
│ ├── formatters.test.ts
│ └── __snapshots__/
│ └── formatters.test.ts.snap
When snapshots fail, you'll see a diff:
- Expected
+ Received
Object {
- "name": "John",
+ "name": "Jane",
}
Common causes:
--update-snapshots)Be aware of platform-specific differences:
// Paths might differ between Windows/Unix
test("file operations", () => {
const result = processFile("./test.txt");
expect({
...result,
path: result.path.replace(/\\/g, "/"), // Normalize paths
}).toMatchSnapshot();
});
development
Using TypeScript with Bun, including type definitions and compiler options
development
Learn how to write tests using Bun's Jest-compatible API with support for async tests, timeouts, and various test modifiers
testing
Learn about Bun test's runtime integration, environment variables, timeouts, and error handling
testing
Test Reporters