TL;DR:

  • MSW (Mock Service Worker) intercepts requests in the browser and Node.js at the network level — best for frontend development and component/integration tests where you want mocks that survive framework changes
  • Mockoon is a desktop app and CLI for running a standalone mock server — best for teams sharing a realistic API mock, or for mocking external APIs you don’t control
  • WireMock is a mature Java-based mock server with powerful request matching and scenario state — best for JVM projects, complex contract testing, and large teams with existing Java tooling
  • All three support recording real API responses and replaying them, which is the fastest way to build realistic mocks

API mocking has solved itself in practice. The old approach — hand-rolling mock objects in your test code, maintaining fixture files that gradually drift from reality — works but doesn’t scale. Modern mocking tools intercept at the network level instead, which means your application code doesn’t know it’s talking to a mock. No special test wiring, no injected dependencies, no leaking test concerns into application logic.

Here’s how the three main tools compare, and how to pick the right one for your situation.

MSW: Mock Service Worker

MSW intercepts HTTP and GraphQL requests using a Service Worker in the browser, and using Node’s HTTP interception in Jest, Vitest, or Playwright tests. The key thing about MSW is where the interception happens — at the network layer, outside your application code. This means your React components, your fetch calls, your axios client all behave exactly as in production. You’re mocking the network, not the code.

Setup (browser):

npm install msw --save-dev
npx msw init public/ --save
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get('/api/users/:id', ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'Jane Smith',
      email: 'jane@example.com',
    })
  }),

  http.post('/api/users', async ({ request }) => {
    const body = await request.json()
    return HttpResponse.json(
      { id: '123', ...body },
      { status: 201 }
    )
  }),
]
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers)
// src/main.ts (or wherever you bootstrap your app)
if (import.meta.env.DEV) {
  const { worker } = await import('./mocks/browser')
  await worker.start()
}

Setup (Node/tests):

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

export const server = setupServer(...handlers)
// vitest.setup.ts
import { server } from './src/mocks/node'
import { beforeAll, afterAll, afterEach } from 'vitest'

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

The same handler definitions work in both browser and test environments. That’s the real advantage of MSW: write your mocks once, use them in Storybook, in development, in Vitest, in Playwright.

MSW is the right choice when:

  • You’re building a frontend and the backend is behind or unstable
  • You want mocks that work both in browser dev and in automated tests without duplication
  • You want to prototype a UI against an API before that API exists
  • Your team works primarily in TypeScript/JavaScript

Limitations: MSW doesn’t help with native mobile apps (no Service Worker), and it’s not the right tool for mocking third-party external services that your backend calls.

Mockoon: Desktop App and CLI Mock Server

Mockoon runs a real HTTP server on your machine (or in CI) that responds to API requests. You configure it through a GUI desktop application and export the configuration as a JSON file that can be committed to your repo and run by your team.

The desktop app is where Mockoon shines. You define routes visually, set response bodies, configure status codes, add templating with Handlebars (randomized data, request value injection), and set up rules so different requests get different responses. Once configured, your team shares the JSON config, and everyone runs the same mock server.

Running via CLI:

npm install -g @mockoon/cli
mockoon-cli start --data ./mock-config.json --port 3001

Or in Docker for CI:

# docker-compose.yml
services:
  mock-api:
    image: mockoon/cli:latest
    command: ["--data", "/data/mock-config.json", "--port", "3001"]
    volumes:
      - ./mock-config.json:/data/mock-config.json
    ports:
      - "3001:3001"

Mockoon supports dynamic responses using Handlebars templating:

{
  "body": "{\"id\": \"{{faker 'string.uuid'}}\", \"name\": \"{{body 'name'}}\", \"created_at\": \"{{now}}\"}"
}

It also supports response rules — conditions that return different responses based on request headers, query params, or body content. This lets you mock authentication (different response for valid vs invalid Bearer token) and error states (different body when a specific ID is requested).

Recording from a real API:

Mockoon’s proxy feature lets you point it at a real API and record live responses. Every request passes through to the real API, and the response is saved as a mock. Once you’ve captured the responses you need, you can run fully offline.

Mockoon is the right choice when:

  • You want a visual interface for building and maintaining mocks
  • You need to share a standardized mock server across a team (commit the JSON config)
  • You’re mocking third-party external APIs that your backend calls
  • You want randomized, realistic fake data in responses

Limitations: Mockoon doesn’t integrate as cleanly with test runners as MSW — you’re running a separate process and pointing your tests at it. Fine for integration tests, overkill for unit tests.

WireMock: Powerful Contract-Level Mocking

WireMock is a mock server for Java/JVM environments that has expanded to support other languages via Docker and a standalone jar. It’s been around since 2012 and is the most mature option for complex matching and stateful scenario testing.

Running WireMock standalone:

# Docker (no JVM needed)
docker run -p 8080:8080 wiremock/wiremock:latest

# Or Java jar
java -jar wiremock-standalone.jar --port 8080

Defining stubs via JSON (place in mappings/ directory):

{
  "request": {
    "method": "GET",
    "urlPattern": "/api/orders/[0-9]+"
  },
  "response": {
    "status": 200,
    "jsonBody": {
      "id": "{{request.pathSegments.[2]}}",
      "status": "shipped",
      "items": [{"sku": "ABC-123", "qty": 2}]
    },
    "transformers": ["response-template"]
  }
}

WireMock’s strongest feature: stateful scenarios. You can model an API that changes state over a sequence of calls:

{
  "scenarioName": "Order Lifecycle",
  "requiredScenarioState": "Started",
  "newScenarioState": "Order Placed",
  "request": { "method": "POST", "url": "/api/orders" },
  "response": { "status": 201, "jsonBody": {"id": "999", "status": "pending"} }
}

Subsequent requests can check for "Order Placed" state and return a different response. This lets you mock multi-step flows — order creation, then order status check, then cancellation — where each step changes what subsequent requests return.

WireMock also has first-class contract testing integration with Pact and Spring Cloud Contract.

WireMock is the right choice when:

  • You’re in a Java/Spring project and want tight JUnit integration
  • You need stateful scenarios that model multi-step workflows
  • You’re doing consumer-driven contract testing
  • You need fine-grained request matching (regex URL patterns, body JSON path matching, header matching)

Limitations: WireMock’s configuration is verbose compared to MSW or Mockoon. The Docker mode removes the JVM dependency, but it’s still heavier to set up than the alternatives.

Recording vs Writing Mocks

All three tools support recording real API responses and replaying them. This is usually faster than writing mocks by hand.

The workflow: point the mock tool at the real API in proxy/record mode, run through the flows you want to mock, stop recording. You now have realistic response bodies with real field names and formats. Edit out any sensitive values (tokens, PII), commit the recorded mocks to source control, and replay them in development and tests.

The trap with recorded mocks is treating them as permanent truth. Real APIs change. Recorded mocks that aren’t refreshed become misleading over time. Build a process for refreshing them periodically, or use schema validation in tests to catch drift.

Choosing

  • Building a React/Vue/Svelte frontend with TypeScript tests? MSW.
  • Shared team mock server, or mocking external third-party APIs your backend calls? Mockoon.
  • Java/Spring project, complex state, contract testing? WireMock.
  • Not sure? Start with MSW for frontend work and Mockoon for everything else — both have low setup overhead and you can graduate to WireMock if you hit the limits.

The underlying principle is the same for all three: mock at the network level, share mock definitions as code, keep mocks close enough to reality to catch real problems. Any of these tools gets you there.