Learn/Practical Guides

Testing with JSON — Fixtures, Mocks & Snapshot Testing

JSON is everywhere in tests — fixture files, mock API responses, snapshot assertions, and factory data. This guide covers practical patterns for organizing test data, mocking APIs with MSW, snapshot testing with Jest/Vitest, and avoiding the most common testing pitfalls.

JSON Test Data Strategies

JSON Testing Approaches

1. Inline JSON in Tests

For small, focused tests, define JSON directly in the test file:

Inline JSON assertiontypescript
1import { describe, it, expect } from 'vitest';
2import { transformUser } from '../utils/transform';
3
4describe('transformUser', () => {
5 it('extracts display name and role', () => {
6 const apiResponse = {
7 id: 42,
8 first_name: 'Alice',
9 last_name: 'Chen',
10 role_id: 1,
11 role_name: 'admin',
12 created_at: '2026-01-15T10:00:00Z',
13 };
14
15 const result = transformUser(apiResponse);
16
17 expect(result).toEqual({
18 id: 42,
19 displayName: 'Alice Chen',
20 role: 'admin',
21 });
22 });
23});

Tip

Use toEqual() for deep equality on JSON-like objects. Use toMatchObject() when you only care about a subset of fields.

2. Fixture Files

When test data is large or shared, extract it to JSON files:

File structuretext
1src/
2 __fixtures__/
3 users/
4 admin.json
5 regular-user.json
6 api-response-list.json
7 products/
8 single-product.json
9 catalog.json
10 components/
11 UserCard.test.tsx
__fixtures__/users/admin.jsonjson
1{
2 "id": 42,
3 "name": "Alice Chen",
4 "email": "[email protected]",
5 "role": "admin",
6 "permissions": ["read", "write", "delete"],
7 "profile": {
8 "avatar": "https://example.com/alice.jpg",
9 "bio": "Senior developer"
10 }
11}
Loading fixtures in teststypescript
1import adminUser from '../__fixtures__/users/admin.json';
2import regularUser from '../__fixtures__/users/regular-user.json';
3
4describe('UserCard', () => {
5 it('shows admin badge for admin users', () => {
6 render(<UserCard user={adminUser} />);
7 expect(screen.getByText('Admin')).toBeInTheDocument();
8 });
9
10 it('hides admin badge for regular users', () => {
11 render(<UserCard user={regularUser} />);
12 expect(screen.queryByText('Admin')).not.toBeInTheDocument();
13 });
14});

3. Factory Functions

Factories generate test data with defaults, letting you override only the fields relevant to each test:

factories/user.tstypescript
1interface User {
2 id: number;
3 name: string;
4 email: string;
5 role: 'admin' | 'editor' | 'viewer';
6 active: boolean;
7 createdAt: string;
8}
9
10let nextId = 1;
11
12export function createUser(overrides: Partial<User> = {}): User {
13 return {
14 id: nextId++,
15 name: 'Test User',
16 email: `user-${nextId}@test.com`,
17 role: 'viewer',
18 active: true,
19 createdAt: '2026-01-01T00:00:00Z',
20 ...overrides,
21 };
22}
Using the factorytypescript
1import { createUser } from '../factories/user';
2
3describe('access control', () => {
4 it('grants delete permission to admins', () => {
5 const admin = createUser({ role: 'admin' });
6 expect(canDelete(admin)).toBe(true);
7 });
8
9 it('denies delete to viewers', () => {
10 const viewer = createUser({ role: 'viewer' });
11 expect(canDelete(viewer)).toBe(false);
12 });
13
14 it('ignores inactive users', () => {
15 const inactive = createUser({ role: 'admin', active: false });
16 expect(canDelete(inactive)).toBe(false);
17 });
18});

Important

Factories keep tests DRY and focused. When the data shape changes, you update one factory instead of dozens of inline objects.

4. Snapshot Testing

Snapshot tests serialize output and compare it against a saved reference:

Jest / Vitest snapshot testtypescript
1import { transformApiResponse } from '../utils/transform';
2import rawResponse from '../__fixtures__/api-response.json';
3
4describe('API response transformation', () => {
5 it('matches saved snapshot', () => {
6 const result = transformApiResponse(rawResponse);
7 expect(result).toMatchSnapshot();
8 });
9
10 it('matches inline snapshot', () => {
11 const result = transformApiResponse({ id: 1, name: 'Test' });
12 expect(result).toMatchInlineSnapshot(`
13 {
14 "id": 1,
15 "displayName": "Test",
16 "slug": "test",
17 }
18 `);
19 });
20});

When to Use Snapshots

Use Snapshots ForAvoid Snapshots For
Serialized output (JSON, HTML)Volatile data (timestamps, random IDs)
API response transformationsUI behavior (clicks, navigation)
Config generationLarge objects with many irrelevant fields
Regression detectionNew feature development (snapshots don't define intent)

Snapshot Best Practices

  • Review snapshot diffs in PRs — do not blindly update
  • Keep snapshots small and focused (use toMatchObject for partial matches)
  • Use inline snapshots for tiny outputs so reviewers see the expectation in the test file
  • Don't snapshot entire API responses — extract and test the fields that matter
  • Don't test implementation details — snapshot the public output

5. Mocking API Responses with MSW

MSW (Mock Service Worker) intercepts HTTP requests and returns mock JSON at the network level:

MSW handler setuptypescript
1import { http, HttpResponse } from 'msw';
2import { setupServer } from 'msw/node';
3
4const handlers = [
5 http.get('/api/users', () => {
6 return HttpResponse.json([
7 { id: 1, name: 'Alice', role: 'admin' },
8 { id: 2, name: 'Bob', role: 'viewer' },
9 ]);
10 }),
11
12 http.get('/api/users/:id', ({ params }) => {
13 if (params.id === '999') {
14 return HttpResponse.json(
15 { error: 'User not found' },
16 { status: 404 }
17 );
18 }
19 return HttpResponse.json({ id: Number(params.id), name: 'Alice' });
20 }),
21
22 http.post('/api/users', async ({ request }) => {
23 const body = await request.json();
24 return HttpResponse.json({ id: 3, ...body }, { status: 201 });
25 }),
26];
27
28export const server = setupServer(...handlers);
Using MSW in teststypescript
1import { server } from './mocks/server';
2
3beforeAll(() => server.listen());
4afterEach(() => server.resetHandlers());
5afterAll(() => server.close());
6
7describe('UserList component', () => {
8 it('renders users from API', async () => {
9 render(<UserList />);
10 expect(await screen.findByText('Alice')).toBeInTheDocument();
11 expect(screen.getByText('Bob')).toBeInTheDocument();
12 });
13
14 it('shows error state for failed requests', async () => {
15 server.use(
16 http.get('/api/users', () => {
17 return HttpResponse.json({ error: 'Server error' }, { status: 500 });
18 })
19 );
20 render(<UserList />);
21 expect(await screen.findByText(/error/i)).toBeInTheDocument();
22 });
23});
MSW Request Interception Flow

6. Contract Testing with JSON Schema

Validate that API responses match an expected structure using JSON Schema:

Contract test with Ajvtypescript
1import Ajv from 'ajv';
2import userSchema from '../schemas/user.json';
3
4const ajv = new Ajv();
5const validate = ajv.compile(userSchema);
6
7describe('User API contract', () => {
8 it('response matches JSON Schema', async () => {
9 const response = await fetch('/api/users/1');
10 const data = await response.json();
11
12 const valid = validate(data);
13 if (!valid) {
14 console.error(validate.errors);
15 }
16 expect(valid).toBe(true);
17 });
18});

Try It — Validate Test Fixture Data

Try It Yourself

A test fixture — experiment with adding or removing fields

Frequently Asked Questions

What is a JSON fixture in testing?
A JSON fixture is a static file containing sample data that tests use as input or expected output. Fixtures live alongside test files (e.g., __fixtures__/user.json) and provide consistent, version-controlled test data.
Should I use inline JSON or fixture files in tests?
Use inline JSON for small, focused assertions (1-5 fields). Use fixture files for complex data structures, shared test data, or when the same data is used across multiple test files. Fixture files are easier to update and review in PRs.
What is JSON snapshot testing?
Snapshot testing serializes an output (often JSON) and saves it to a .snap file. On subsequent runs, the test compares the current output to the saved snapshot. If they differ, the test fails. You can update snapshots intentionally when behavior changes.
How do I mock API responses in tests?
Use tools like MSW (Mock Service Worker) to intercept network requests and return JSON responses. MSW works in both Node.js (for unit tests) and the browser (for integration tests). Define handlers that match URL patterns and return mock JSON.
What is a JSON factory pattern?
A factory function generates test data with sensible defaults, allowing you to override specific fields per test. This avoids duplicating large JSON objects across tests while keeping each test focused on the fields it cares about.