🆕 New in v0.13.0
Snapshot testing lets you capture the expected output of your code and automatically compare it against future runs. Instead of manually writing assertions for complex objects, arrays, or rendered output, you create a "snapshot" that gets stored alongside your tests.
Currently Supported Test Frameworks:
assert.snapshot() integration✏️ Aliases:
{unknown} to match snapshot {test-context | string} {unknown} to match the snapshot {test-context | string} {unknown} to equal snapshot {test-context | string} {unknown} to equal the snapshot {test-context | string}
Asserts that a value matches a stored snapshot. The snapshot is automatically created on first run and validated on subsequent runs.
Basic Usage with node:test:
import test from 'node:test';
import { expect } from 'bupkis';
test('component renders correctly', (t) => {
const output = {
type: 'div',
props: { className: 'container' },
children: ['Hello, World!'],
};
expect(output, 'to match snapshot', t);
});
Success (first run - creates snapshot):
test('user profile', (t) => {
const user = { name: 'Alice', age: 30, role: 'admin' };
expect(user, 'to match snapshot', t);
});
// ✓ Snapshot created
Success (subsequent runs - matches snapshot):
test('user profile', (t) => {
const user = { name: 'Alice', age: 30, role: 'admin' };
expect(user, 'to match snapshot', t);
});
// ✓ Matches snapshot
Failure (value changed):
test('user profile', (t) => {
const user = { name: 'Alice', age: 31, role: 'admin' }; // age changed!
expect(user, 'to match snapshot', t);
});
// AssertionError: Snapshot does not match
// - expected
// + actual
//
// Object {
// "name": "Alice",
// - "age": 30,
// + "age": 31,
// "role": "admin"
// }
Negation:
// Asserts that the value does NOT match the snapshot
expect(changedOutput, 'not to match snapshot', t);
With Mocha:
describe('MyComponent', function () {
it('renders correctly', function () {
const output = renderComponent();
expect(output, 'to match snapshot', this); // Pass Mocha context
});
});
With Explicit Snapshot Name:
test('any framework', () => {
const output = renderComponent();
// Use string name instead of test context
expect(output, 'to match snapshot', 'component-default-state');
});
✏️ Aliases:
{unknown} to match snapshot {test-context | string} with options {options} {unknown} to match the snapshot {test-context | string} with options {options} {unknown} to equal snapshot {test-context | string} with options {options} {unknown} to equal the snapshot {test-context | string} with options {options}
Extended version that accepts custom serialization and naming options via the with options syntax.
Options Object:
serializer?: (value: any) => string - Custom function to serialize the value before snapshottinghint?: string - Additional identifier for multiple snapshots in the same testCustom Serialization:
test('redacts sensitive data', (t) => {
const user = {
username: 'alice',
password: 'secret123',
email: 'alice@example.com',
};
expect(user, 'to match snapshot', t, 'with options', {
serializer: (value) =>
JSON.stringify({ ...value, password: '[REDACTED]' }, null, 2),
});
});
// Snapshot will contain:
// {
// "username": "alice",
// "password": "[REDACTED]",
// "email": "alice@example.com"
// }
Multiple Snapshots Per Test:
test('multi-step workflow', (t) => {
const step1 = { status: 'pending', progress: 0 };
expect(step1, 'to match snapshot', t, 'with options', { hint: 'step-1' });
const step2 = { status: 'processing', progress: 50 };
expect(step2, 'to match snapshot', t, 'with options', { hint: 'step-2' });
const step3 = { status: 'complete', progress: 100 };
expect(step3, 'to match snapshot', t, 'with options', { hint: 'step-3' });
});
Custom Serialization for Dates:
test('handles timestamps', (t) => {
const data = {
id: 123,
createdAt: new Date('2024-01-15T10:30:00Z'),
updatedAt: new Date('2024-01-16T14:45:00Z'),
};
expect(data, 'to match snapshot', t, 'with options', {
serializer: (value) => {
const normalized = {
...value,
createdAt: value.createdAt.toISOString(),
updatedAt: value.updatedAt.toISOString(),
};
return JSON.stringify(normalized, null, 2);
},
});
});
When your code intentionally changes, you need to update the stored snapshots:
node:test:
node --test --test-update-snapshots
Other frameworks:
BUPKIS_UPDATE_SNAPSHOTS=1 npm test
Jest/Vitest (when supported):
vitest -u
jest -u
Snapshot assertions can be chained with other assertions using and:
test('validates structure and snapshots', (t) => {
const user = {
name: 'Alice',
email: 'alice@example.com',
age: 30,
roles: ['admin', 'user'],
};
expect(
user,
'to be an object',
'and',
'to have property',
'email',
'and',
'to match snapshot',
t,
);
});
hint option when creating multiple snapshots in one test❌ Don't snapshot volatile data without serialization:
// BAD - timestamp will fail on every run
const data = { timestamp: Date.now(), value: 'test' };
expect(data, 'to match snapshot', t);
✅ Use a serializer to normalize:
// GOOD - normalize timestamp
expect(data, 'to match snapshot', t, 'with options', {
serializer: (value) =>
JSON.stringify({ ...value, timestamp: '[TIMESTAMP]' }, null, 2),
});
❌ Don't create overly large snapshots:
// BAD - entire API response with 1000+ items
expect(massiveApiResponse, 'to match snapshot', t);
✅ Snapshot a representative subset:
// GOOD - snapshot structure and first few items
const subset = {
meta: massiveApiResponse.meta,
items: massiveApiResponse.items.slice(0, 3),
};
expect(subset, 'to match snapshot', t);