Skip to main content

API Mocking for Development: MSW vs Mirage vs WireMock

·APIScout Team
mockingtestingmswdevelopmentapi integration

API Mocking for Development: MSW vs Mirage vs WireMock

Developing against live APIs is slow, costs money, and breaks when the API goes down. API mocking lets you develop, test, and demo without touching real APIs. But which mocking tool should you use? It depends on your stack, test runner, and what you're building.

The Landscape

ToolLanguageLayerBest For
MSW (Mock Service Worker)JS/TSNetwork (Service Worker / Node)Frontend + API testing
Mirage JSJS/TSApplication (in-memory)Frontend prototyping
WireMockJava (HTTP server)Network (HTTP proxy)Backend / language-agnostic
PrismCLINetwork (HTTP server)OpenAPI-driven mocking
json-serverJSNetwork (HTTP server)Quick REST API from JSON
NockNode.jsNetwork (Node http)Node.js unit tests
Polly.jsJS/TSNetwork (record/replay)Snapshot-based testing

MSW (Mock Service Worker)

What It Is

MSW intercepts HTTP requests at the network level using a Service Worker (browser) or request interceptor (Node.js). Your application code makes real fetch calls — MSW catches them before they leave.

Setup

npm install msw --save-dev
npx msw init public/ --save  # For browser usage
// mocks/handlers.ts
import { http, HttpResponse } from 'msw';

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

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

  // Dynamic route params
  http.get('https://api.example.com/users/:id', ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'Alice',
      email: 'alice@example.com',
    });
  }),

  // Error simulation
  http.delete('https://api.example.com/users/:id', () => {
    return HttpResponse.json(
      { error: 'Forbidden' },
      { status: 403 }
    );
  }),
];
// For tests (Node.js)
// mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);

// vitest.setup.ts
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// For browser (development)
// mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);

// main.tsx
if (process.env.NODE_ENV === 'development') {
  const { worker } = await import('./mocks/browser');
  await worker.start();
}

When to Use MSW

  • ✅ Frontend development without backend
  • ✅ Integration tests (React Testing Library, Vitest, Jest)
  • ✅ E2E test data setup
  • ✅ Shared handlers between dev and test
  • ❌ Backend-only mocking (use WireMock or Nock)
  • ❌ Need persistent data across requests (use Mirage)

Mirage JS

What It Is

Mirage runs an in-memory server inside your JavaScript application. It includes a full ORM with relationships, factories, and serializers — making it ideal for prototyping with realistic data.

Setup

npm install miragejs --save-dev
import { createServer, Model, Factory, belongsTo, hasMany } from 'miragejs';

const server = createServer({
  models: {
    user: Model.extend({
      posts: hasMany(),
    }),
    post: Model.extend({
      author: belongsTo('user'),
    }),
  },

  factories: {
    user: Factory.extend({
      name(i: number) { return `User ${i}`; },
      email(i: number) { return `user${i}@example.com`; },
    }),
    post: Factory.extend({
      title(i: number) { return `Post ${i}`; },
      body() { return 'Lorem ipsum...'; },
    }),
  },

  seeds(server) {
    // Create 10 users with 3 posts each
    server.createList('user', 10).forEach(user => {
      server.createList('post', 3, { author: user });
    });
  },

  routes() {
    this.namespace = 'api';

    this.get('/users', (schema) => {
      return schema.users.all();
    });

    this.get('/users/:id', (schema, request) => {
      return schema.users.find(request.params.id);
    });

    this.post('/users', (schema, request) => {
      const attrs = JSON.parse(request.requestBody);
      return schema.users.create(attrs);
    });

    this.del('/users/:id', (schema, request) => {
      schema.users.find(request.params.id)?.destroy();
      return new Response(204);
    });
  },
});

When to Use Mirage

  • ✅ Prototyping with realistic, relational data
  • ✅ Frontend demos and presentations
  • ✅ Complex CRUD flows (create → list → update → delete)
  • ✅ Need stateful mock data (data persists during session)
  • ❌ Node.js / backend testing (browser-only)
  • ❌ Real network-level mocking (uses XMLHttpRequest patching)

WireMock

What It Is

WireMock is an HTTP server that acts as a mock API. It runs as a standalone process and intercepts real HTTP traffic — making it language-agnostic.

Setup

# Docker
docker run -d -p 8080:8080 wiremock/wiremock

# Or standalone JAR
java -jar wiremock-standalone-3.x.jar --port 8080
# Define mocks via API
curl -X POST http://localhost:8080/__admin/mappings -d '{
  "request": {
    "method": "GET",
    "urlPattern": "/api/users/.*"
  },
  "response": {
    "status": 200,
    "headers": { "Content-Type": "application/json" },
    "jsonBody": {
      "id": "123",
      "name": "Alice",
      "email": "alice@example.com"
    }
  }
}'

# Or via JSON files in mappings/ directory
# mappings/get-user.json
{
  "request": {
    "method": "GET",
    "urlPathPattern": "/api/users/[a-z0-9]+"
  },
  "response": {
    "status": 200,
    "headers": { "Content-Type": "application/json" },
    "jsonBody": {
      "id": "{{request.path.[2]}}",
      "name": "Alice"
    },
    "transformers": ["response-template"]
  }
}

When to Use WireMock

  • ✅ Backend integration testing (any language)
  • ✅ Contract testing
  • ✅ CI/CD pipelines (Docker-based)
  • ✅ Team-wide shared mock server
  • ❌ Quick frontend prototyping (too heavy)
  • ❌ Browser-based testing (use MSW)

Prism (OpenAPI-Driven)

What It Is

Prism generates a mock server directly from your OpenAPI specification — zero handler code.

# Install
npm install -g @stoplight/prism-cli

# Run mock server from OpenAPI spec
prism mock openapi.yaml
# Server starts at http://127.0.0.1:4010

# Request validation mode
prism proxy openapi.yaml https://api.example.com
# Validates requests/responses against spec

When to Use Prism

  • ✅ API-first development (design spec → mock → build)
  • ✅ Validating requests against OpenAPI spec
  • ✅ Quick mock from existing spec (zero code)
  • ❌ Custom mock logic (limited)
  • ❌ Stateful data (no persistence)

Comparison Matrix

FeatureMSWMirageWireMockPrism
Setup complexityLowLowMediumLow
Browser support
Node.js support✅ (HTTP)✅ (HTTP)
Network-level interception
Stateful data / ORM
OpenAPI integration
Record/replay
Language agnosticJS/TS onlyJS/TS only
TypeScript typesPartial
Bundle size~12KB~40KBN/A (server)N/A (CLI)

Choosing the Right Tool

ScenarioRecommended Tool
React/Vue/Svelte developmentMSW
Prototyping with complex dataMirage JS
Backend integration tests (any language)WireMock
API-first development with OpenAPIPrism
Quick REST mock from JSONjson-server
Node.js unit testsNock or MSW
Snapshot-based test replayPolly.js

Common Mistakes

MistakeImpactFix
Mocking too muchTests pass but integration breaksMock at network boundary, not function level
Not testing error casesOnly happy path worksAdd mock handlers for 4xx, 5xx, timeouts
Stale mock dataMock doesn't match real APIRefresh mocks when API changes
Mocking in productionUsers get fake dataOnly enable mocks in dev/test environments
Not matching real response shapeFrontend breaks with real APIUse OpenAPI spec to validate mock responses

Find APIs with the best testing and sandbox support on APIScout — sandbox environments, mock data generators, and developer experience ratings.

Comments