---
title: "Mock API for testing: Prism, WireMock, MSW, json-server"
description: "Develop and test against a local mock API instead of a placeholder address: Prism from an OpenAPI file, WireMock, MSW and json-server, with examples."
url: https://example-petstore.com/guides/mock-api
language: en
---

# Test against a mock API, not a placeholder

Code that points at a made-up address such as api.example-petstore.com still sends real requests, and they reach whoever owns that name. A mock API gives the same convenience without that risk: it runs on your own machine or in your tests, answers instantly, and always returns the data you expect.

## Why not a placeholder

- A placeholder that looks like a real domain may be registered by someone else. Every request, including headers with keys or tokens, then reaches their server.
- Unused names under `example.com`, such as `api.example.com`, do not resolve at all, so the code fails with a timeout or DNS error instead of showing how it handles real answers.
- Tests that depend on a public server are slow and flaky, and they break when that server changes.

A mock API solves all three: it listens on `localhost` or inside the test process, and nothing leaves your machine.

## Which tool when

| Tool | Best for | Runs as |
| --- | --- | --- |
| Prism | You have an OpenAPI description and want answers that follow it | Local server (Node.js or Docker), port 4010 |
| WireMock | Exact, recorded answers, error cases and delays for any language | Local server (Java or Docker), port 8080 |
| MSW | JavaScript and TypeScript: front-end development and unit tests | Inside the browser or the Node.js test process |
| json-server | A working REST API from one JSON file, for prototypes | Local server (Node.js), port 3000 |

For the Swagger Petstore sample API itself, run the official image locally; see [Looking for the Swagger Petstore?](https://example-petstore.com/guides/swagger-petstore)

## Prism: from an OpenAPI file

Prism reads an OpenAPI (or Swagger 2.0) description and answers every operation in it with the examples or schemas from that file. Requests that do not match the description get a clear validation error, which makes it useful for checking a client before the real API exists.

```
# With Node.js
npm install -g @stoplight/prism-cli
prism mock openapi.yaml

# With Docker
docker run --init --rm -v "$(pwd)":/tmp -p 4010:4010 stoplight/prism:5 mock -h 0.0.0.0 /tmp/openapi.yaml

# Then
curl http://127.0.0.1:4010/pets
curl http://127.0.0.1:4010/pets/1 -H "Prefer: code=404"
```

The `Prefer` header chooses a specific response from the description, such as an error code or a named example. With `--dynamic` (`-d`) Prism generates fresh data from the schemas on every request.

## WireMock: recorded answers

WireMock answers from stub files that you write or record. It suits any language, and it can simulate slow responses and failures that a real service rarely produces on demand.

```
# mocks/mappings/pet.json
{
  "request":  { "method": "GET", "url": "/v1/pets/1" },
  "response": {
    "status": 200,
    "headers": { "Content-Type": "application/json" },
    "jsonBody": { "id": 1, "name": "Rex", "status": "available" }
  }
}

# Start it with the folder that holds mappings/ (and __files/ for larger bodies)
docker run -it --rm -p 8080:8080 -v "$(pwd)/mocks":/home/wiremock wiremock/wiremock

curl http://localhost:8080/v1/pets/1
```

Add `"fixedDelayMilliseconds": 3000` to a response to test timeouts, or a status such as `503` to test retries. The admin API at `/__admin` lets tests add stubs and check which requests arrived.

## MSW and PHP mocks: inside your tests

Mock Service Worker intercepts requests inside the application itself: in the browser through a service worker, in Node.js inside the test process. The code under test keeps calling its normal base URL; no extra server runs.

```
// handlers.js
import { http, HttpResponse } from 'msw'

export const handlers = [
  http.get('https://api.example.com/v1/pets/:id', ({ params }) =>
    HttpResponse.json({ id: Number(params.id), name: 'Rex', status: 'available' })),
  http.post('https://api.example.com/v1/pets', () =>
    HttpResponse.json({ error: 'name is required' }, { status: 400 })),
]

// In tests (Node.js)
import { setupServer } from 'msw/node'
const server = setupServer(...handlers)
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
```

`onUnhandledRequest: 'error'` makes a test fail as soon as code calls an address without a handler, so a forgotten placeholder shows up in the test run instead of in production. In the browser, run `npx msw init public/` once and start the worker from `msw/browser`.

In PHP tests, Guzzle’s `MockHandler` does the same inside the test process, and Symfony has `MockHttpClient`:

```
// PHP, Guzzle: answers in order, without a network
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;

$mock = new MockHandler([
    new Response(200, ['Content-Type' => 'application/json'], '{"id": 1, "name": "Rex", "status": "available"}'),
    new Response(400, [], '{"error": "name is required"}'),
]);
$client = new Client(['handler' => HandlerStack::create($mock), 'base_uri' => 'https://api.example.com/v1/']);

// PHP, Symfony
$client = new Symfony\Component\HttpClient\MockHttpClient(
    [new Symfony\Component\HttpClient\Response\MockResponse('{"id": 1, "name": "Rex"}')],
    'https://api.example.com/v1/'
);
```

The address in the handlers is `api.example.com`: a reserved name that never reaches a real server, even when a request slips past the mock.

## json-server: a quick REST API

json-server turns one JSON file into a REST API with list, detail, create, update and delete routes. Changes are written back to the file, which makes it handy for prototypes and demos.

```
# db.json
{
  "pets":   [ { "id": "1", "name": "Rex", "status": "available" } ],
  "orders": []
}

npx json-server db.json

curl http://localhost:3000/pets
curl -X POST http://localhost:3000/orders -H "Content-Type: application/json" -d '{"petId": "1"}'
```

It has no validation or authentication, so keep it to prototypes; for contract tests, Prism or WireMock fit better.

## Switching to the real API

Keep the base URL in configuration, with the mock as the value for development and tests, and the real address only where the application actually runs:

```
# .env.development
API_BASE_URL=http://localhost:4010

# .env.test
API_BASE_URL=http://localhost:8080

# production: set in the hosting environment, never in the repository
API_BASE_URL=https://api.your-real-service.com
```

More on this, including a check that stops example addresses from reaching production: [Configuring API clients and SDKs](https://example-petstore.com/guides/api-base-url).

## Related guides

### [Configuring API clients and SDKs](https://example-petstore.com/guides/api-base-url)

Keep base URLs out of code, point them at the real service, and add a check that stops example addresses from reaching production.

### [Looking for the Swagger Petstore?](https://example-petstore.com/guides/swagger-petstore)

The real base URLs of the Swagger Petstore sample API, working requests, the test key, and how to run your own copy with Docker.

### [Example values that are safe to use](https://example-petstore.com/guides/example-values)

Reserved domains, IP ranges, AS numbers, MAC addresses, phone numbers and test cards for examples that stay harmless.

## Sources

- [Prism](https://github.com/stoplightio/prism) Stoplight
- [WireMock Docker images](https://github.com/wiremock/wiremock-docker) WireMock
- [Mock Service Worker](https://mswjs.io/docs/) MSW
- [json-server](https://github.com/typicode/json-server) GitHub
