Best API Mocking Libraries for JavaScript Testing in 2026
·PkgPulse Team
TL;DR
MSW (Mock Service Worker) is the 2026 standard for API mocking. MSW (~5M weekly downloads) intercepts requests at the network level using Service Workers (browser) or Node.js interceptors — your code never knows it's mocked. nock (~8M downloads) is Node.js-only and intercepts http/https module calls. Mirage.js (~400K) is browser-focused with a fake database layer. For most projects, MSW handles both browser and Node.js test environments seamlessly.
Key Takeaways
- MSW: ~5M weekly downloads — browser + Node.js, network-level interception, no code changes
- nock: ~8M downloads — Node.js only, intercepts http.request, widely used in legacy code
- Mirage: ~400K downloads — browser-only, in-memory DB, REST + GraphQL
- MSW v2 — native fetch interception, TypeScript-first, no polyfills needed
- @mswjs/data — MSW companion for typed fake database
MSW (Mock Service Worker)
// MSW — define handlers once, use in browser + tests
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
// GET handler
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
]);
}),
// GET with path params
http.get('/api/users/:id', ({ params }) => {
const { id } = params;
return HttpResponse.json({
id: Number(id),
name: 'Alice',
email: 'alice@example.com',
});
}),
// POST handler
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json(
{ id: Date.now(), ...body },
{ status: 201 }
);
}),
// Error simulation
http.delete('/api/users/:id', () => {
return HttpResponse.json(
{ message: 'Unauthorized' },
{ status: 401 }
);
}),
];
// MSW — browser setup (public/mockServiceWorker.js generated by MSW)
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
// main.tsx — start worker in development
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./mocks/browser');
await worker.start({ onUnhandledRequest: 'bypass' });
}
// MSW — Node.js test setup (Vitest/Jest)
// src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// vitest.setup.ts
import { server } from './src/mocks/server';
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// MSW — override handlers in individual tests
import { server } from '../mocks/server';
import { http, HttpResponse } from 'msw';
describe('UserProfile', () => {
it('shows loading state', async () => {
server.use(
http.get('/api/users/:id', async () => {
await delay(500); // Simulate slow network
return HttpResponse.json({ id: 1, name: 'Alice' });
})
);
render(<UserProfile userId={1} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
it('shows error state', async () => {
server.use(
http.get('/api/users/:id', () => {
return HttpResponse.json(
{ message: 'Not found' },
{ status: 404 }
);
})
);
render(<UserProfile userId={999} />);
await waitFor(() => {
expect(screen.getByText('User not found')).toBeInTheDocument();
});
});
});
nock (Node.js)
// nock — intercepts Node.js http.request
import nock from 'nock';
import axios from 'axios';
describe('UserService', () => {
afterEach(() => nock.cleanAll());
it('fetches a user', async () => {
// Setup mock
nock('https://api.example.com')
.get('/users/1')
.reply(200, { id: 1, name: 'Alice' });
// Call function under test (which uses axios internally)
const user = await getUser(1);
expect(user.name).toBe('Alice');
// nock verifies the request was made
});
it('handles network errors', async () => {
nock('https://api.example.com')
.get('/users/1')
.replyWithError('Network failure');
await expect(getUser(1)).rejects.toThrow('Network failure');
});
it('handles delayed responses', async () => {
nock('https://api.example.com')
.get('/users')
.delay(200) // 200ms delay
.reply(200, []);
const users = await listUsers();
expect(users).toEqual([]);
});
});
// nock — with query params and headers
nock('https://api.example.com')
.get('/users')
.query({ page: '1', limit: '10' }) // Match query params
.matchHeader('Authorization', /^Bearer .+/) // Match header
.reply(200, { users: [], total: 0 });
// POST with body matching
nock('https://api.example.com')
.post('/users', { name: 'Alice', email: /alice@/ }) // Body matcher
.reply(201, { id: 123 });
MSW + @mswjs/data (Typed Mock DB)
// @mswjs/data — in-memory typed database for MSW
import { factory, primaryKey, nullable } from '@mswjs/data';
// Define your data model
const db = factory({
user: {
id: primaryKey(String),
name: String,
email: String,
role: String,
createdAt: nullable(Date),
},
post: {
id: primaryKey(String),
title: String,
body: String,
authorId: String,
},
});
// Seed data
db.user.create({ id: '1', name: 'Alice', email: 'alice@example.com', role: 'admin' });
db.user.create({ id: '2', name: 'Bob', email: 'bob@example.com', role: 'user' });
// Generate handlers from the db
export const handlers = [
...db.user.toHandlers('rest', 'https://api.example.com'),
// Auto-generates: GET /users, GET /users/:id, POST /users, PATCH /users/:id, DELETE /users/:id
];
Comparison Table
| Library | Browser | Node.js | GraphQL | TypeScript | Network Level |
|---|---|---|---|---|---|
| MSW | ✅ | ✅ | ✅ | ✅ | ✅ (ServiceWorker/interceptors) |
| nock | ❌ | ✅ | ✅ | ✅ | ✅ (http module) |
| Mirage | ✅ | ❌ | ✅ | ✅ | ✅ (pretender) |
| fetch-mock | ✅ | ✅ | ❌ | ✅ | fetch only |
When to Choose
| Scenario | Pick |
|---|---|
| New project, browser + Node.js tests | MSW |
| Want to use mocks in browser during dev | MSW |
| Legacy Node.js project (fetch not used) | nock |
| Backend microservice testing | nock |
| Complex mock database with relations | MSW + @mswjs/data |
| Frontend-only, complex server state | Mirage.js |
Compare API mocking library package health on PkgPulse.
See the live comparison
View msw vs. nock on PkgPulse →