Skip to main content

Best npm Packages for API Testing and Mocking in 2026

·PkgPulse Team
0

MSW (Mock Service Worker) intercepts requests at the network level — the same mock works in your browser, Node.js tests, and React Native. Supertest lets you test your Express or Fastify routes without spinning up a real server. Nock intercepts Node.js HTTP requests at the module level. These tools solve different layers of API testing: mocking external APIs vs. testing your own API routes vs. intercepting outbound HTTP.

TL;DR

MSW for mocking third-party APIs in tests and development — the 2026 standard for isomorphic HTTP mocking. Supertest for integration testing your own API routes — fast, simple, no server needed. Nock for intercepting HTTP calls in Node.js tests when MSW is overkill. Playwright for full end-to-end API testing with real HTTP. The right choice depends on what you're testing: your API or someone else's.

Key Takeaways

  • MSW: 5M weekly downloads, intercepts at service worker/Node.js http module level, isomorphic
  • Supertest: 8M weekly downloads, tests Express/Fastify/Hono routes without a real server
  • Nock: 6M weekly downloads, Node.js HTTP interceptor, works with any HTTP library
  • MSW v2: Native fetch interception, Node.js 18+ compatible, no polyfills needed
  • Supertest: Uses the same expect API as Jest/Vitest, chainable assertions
  • All tools: Work alongside Jest, Vitest, or any test runner

The API Testing Landscape

API testing in Node.js apps has two distinct problems:

  1. Testing your own API: Does /api/users return the right data?
  2. Testing code that calls external APIs: Does your service correctly handle Stripe, SendGrid, GitHub API responses?

Different tools solve different parts of this:

Your API routes → Supertest
External API calls (HTTP) → MSW or Nock
Full browser + network → Playwright
Type-safe API contracts → ts-rest + OpenAPI

MSW (Mock Service Worker)

Package: msw Weekly downloads: 5M GitHub stars: 16K Creator: Artem Zakharchenko

MSW is the most sophisticated HTTP mocking library. It intercepts requests at the network level — using a Service Worker in the browser and Node.js's http module in tests — meaning you mock at the same level the browser does.

Installation

npm install -D msw
# For browser usage:
npx msw init public/ --save

Defining Handlers

// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  // Mock GET /api/users
  http.get('/api/users', () => {
    return HttpResponse.json([
      { id: '1', name: 'Alice', email: 'alice@example.com' },
      { id: '2', name: 'Bob', email: 'bob@example.com' },
    ]);
  }),

  // Mock POST /api/users
  http.post('/api/users', async ({ request }) => {
    const body = await request.json() as { name: string; email: string };
    return HttpResponse.json(
      { id: '3', ...body },
      { status: 201 }
    );
  }),

  // Mock external API (Stripe)
  http.post('https://api.stripe.com/v1/charges', () => {
    return HttpResponse.json({
      id: 'ch_test_123',
      status: 'succeeded',
      amount: 2000,
    });
  }),

  // Simulate error
  http.get('/api/products/:id', ({ params }) => {
    if (params.id === '999') {
      return new HttpResponse(null, { status: 404 });
    }
    return HttpResponse.json({ id: params.id, name: 'Widget' });
  }),
];

Node.js Setup (Tests)

// src/mocks/node.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);
// vitest.setup.ts / jest.setup.ts
import { server } from './src/mocks/node';

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Using MSW in Tests

// payment.service.test.ts
import { describe, it, expect } from 'vitest';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/node';
import { PaymentService } from './payment.service';

describe('PaymentService', () => {
  it('processes payment successfully', async () => {
    // Handlers set up in vitest.setup.ts handle this automatically
    const service = new PaymentService();
    const result = await service.charge({ amount: 2000, currency: 'usd' });

    expect(result.status).toBe('succeeded');
    expect(result.id).toBe('ch_test_123');
  });

  it('handles payment failure', async () => {
    // Override handler for this test
    server.use(
      http.post('https://api.stripe.com/v1/charges', () => {
        return HttpResponse.json(
          { error: { message: 'Card declined' } },
          { status: 402 }
        );
      })
    );

    const service = new PaymentService();
    await expect(service.charge({ amount: 2000, currency: 'usd' }))
      .rejects.toThrow('Card declined');
  });
});

Browser Setup (Development)

// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);
// src/main.tsx
if (process.env.NODE_ENV === 'development') {
  const { worker } = await import('./mocks/browser');
  await worker.start({ onUnhandledRequest: 'bypass' });
}

In development, every API call is intercepted by the Service Worker — no backend needed for frontend development.

MSW v2 Key Features

  • Native fetch: Intercepts fetch() natively without polyfills (Node.js 18+)
  • WebSocket mocking: Mock WebSocket connections (v2.3+)
  • GraphQL support: graphql.query, graphql.mutation handlers
  • Request passthrough: passthrough() for letting specific requests reach the real server

MSW Strengths

  • Same handlers work in browser, Node.js, and React Native
  • Intercepts at network level — your code doesn't know it's mocked
  • Excellent DX: readable handler definitions, TypeScript support
  • Works with any HTTP library (fetch, axios, ky, got, node-fetch)

Supertest

Package: supertest Weekly downloads: 8M GitHub stars: 14K

Supertest lets you test HTTP servers (Express, Fastify, Hono, Koa) without starting a real server. It binds to a random port, sends requests, and returns typed responses.

Installation

npm install -D supertest @types/supertest

Testing Express Routes

// app.ts
import express from 'express';

const app = express();
app.use(express.json());

app.get('/users/:id', async (req, res) => {
  const user = await db.users.findById(req.params.id);
  if (!user) return res.status(404).json({ message: 'Not found' });
  res.json(user);
});

app.post('/users', async (req, res) => {
  const user = await db.users.create(req.body);
  res.status(201).json(user);
});

export default app;
// app.test.ts
import request from 'supertest';
import app from './app';

describe('Users API', () => {
  it('GET /users/:id returns user', async () => {
    const response = await request(app)
      .get('/users/123')
      .expect(200)                         // Status assertion
      .expect('Content-Type', /json/);     // Header assertion

    expect(response.body.name).toBe('Alice');
    expect(response.body.id).toBe('123');
  });

  it('GET /users/:id returns 404 for unknown id', async () => {
    await request(app)
      .get('/users/9999')
      .expect(404)
      .expect({ message: 'Not found' });
  });

  it('POST /users creates user', async () => {
    const response = await request(app)
      .post('/users')
      .send({ name: 'Bob', email: 'bob@example.com' })
      .set('Authorization', 'Bearer test-token')
      .expect(201);

    expect(response.body.id).toBeDefined();
    expect(response.body.name).toBe('Bob');
  });

  it('POST /users validates input', async () => {
    await request(app)
      .post('/users')
      .send({ name: '' })  // Missing email
      .expect(400);
  });
});

Supertest with Supertest Session (Auth)

import request from 'supertest-session';

const session = request(app);

it('authenticated routes work', async () => {
  // Login first
  await session
    .post('/auth/login')
    .send({ email: 'admin@example.com', password: 'password' })
    .expect(200);

  // Session maintained across requests
  await session
    .get('/admin/users')
    .expect(200);
});

Supertest Strengths

  • No server needed — works on the Express app object directly
  • Chainable assertions (.expect(200).expect('Content-Type', /json/))
  • Works with any Node.js HTTP framework
  • Simple API — minimal learning curve
  • 8M weekly downloads — most-used HTTP testing library

Supertest Limitations

  • Node.js only (not browser)
  • Tests your API implementation, not external API behavior
  • No built-in mock for external dependencies (combine with MSW/Nock)

Nock

Package: nock Weekly downloads: 6M GitHub stars: 12.5K

Nock intercepts Node.js's http and https modules, blocking real HTTP calls and returning mocked responses.

Installation

npm install -D nock

Basic Usage

import nock from 'nock';

describe('GitHub integration', () => {
  afterEach(() => {
    nock.cleanAll();
  });

  it('fetches repositories', async () => {
    // Intercept: GET https://api.github.com/users/octocat/repos
    nock('https://api.github.com')
      .get('/users/octocat/repos')
      .reply(200, [
        { id: 1, name: 'Hello-World', full_name: 'octocat/Hello-World' },
      ]);

    const repos = await githubClient.getUserRepos('octocat');
    expect(repos).toHaveLength(1);
    expect(repos[0].name).toBe('Hello-World');
  });

  it('handles API rate limiting', async () => {
    nock('https://api.github.com')
      .get('/users/octocat/repos')
      .reply(403, { message: 'API rate limit exceeded' });

    await expect(githubClient.getUserRepos('octocat'))
      .rejects.toThrow('rate limit exceeded');
  });
});

Nock vs MSW

// Nock: intercepts at Node.js http module level
// - Only works in Node.js
// - No browser support
// - Simpler for pure Node.js testing

// MSW: intercepts at service worker / Node.js level
// - Works in both browser and Node.js
// - Same handlers for both environments
// - More complex setup

// When to use Nock:
// - Pure Node.js services, no browser testing needed
// - Already heavily invested in Nock patterns
// - Simpler integration for specific Node.js HTTP scenarios

Other Tools Worth Knowing

Playwright for API Testing

// playwright.config.ts — API testing without a browser
import { defineConfig } from '@playwright/test';
export default defineConfig({ testDir: './tests' });

// api.spec.ts
import { test, expect } from '@playwright/test';

test('users API returns 200', async ({ request }) => {
  const response = await request.get('http://localhost:3000/api/users');
  expect(response.ok()).toBeTruthy();
  const users = await response.json();
  expect(users).toBeInstanceOf(Array);
});

test('create user', async ({ request }) => {
  const response = await request.post('http://localhost:3000/api/users', {
    data: { name: 'Alice', email: 'alice@example.com' },
  });
  expect(response.status()).toBe(201);
  const user = await response.json();
  expect(user.id).toBeDefined();
});

Playwright's request context for API testing is excellent when you want to test against a running server with real HTTP.

Undici for Low-Level HTTP Assertions

import { MockAgent, setGlobalDispatcher } from 'undici';

const mockAgent = new MockAgent();
setGlobalDispatcher(mockAgent);

const mockPool = mockAgent.get('https://api.example.com');
mockPool.intercept({ path: '/users', method: 'GET' })
  .reply(200, [{ id: 1, name: 'Alice' }]);

Undici is Node.js's built-in HTTP library. Its mock agent is useful for testing code that uses native fetch in Node.js 22+.

Choosing Your API Testing Tools

ToolTests your APIMocks external APIsBrowser support
SupertestYesNoNo
MSWPartialYesYes
NockNoYesNo
Playwright APIYesVia route.fulfillYes

Recommended combination for most projects:

// Integration: Supertest for your routes
// External APIs: MSW (same handlers in browser dev mode and Node.js tests)
// E2E: Playwright for real HTTP testing

// vitest.setup.ts
import { server } from './mocks/node'; // MSW
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

// api.test.ts — Supertest for your routes
const response = await request(app).get('/api/users').expect(200);

// users.service.test.ts — MSW for external APIs
// Stripe API is mocked via MSW handlers automatically

Compare testing package downloads on PkgPulse.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.