Why Mock Servers
Mock servers solve a fundamental coordination problem in API development: the frontend team needs the API to exist before the backend team has built it. They also enable testing error scenarios that are hard to trigger reliably against a real service — 503s, 429s, network timeouts, malformed responses.
A mock server intercepts HTTP requests and returns pre-configured responses. The calling code cannot tell the difference. This unlocks:
- Parallel development: Frontend and backend teams work simultaneously
- Offline development: No VPN, no staging environment required
- Deterministic tests: No flaky network calls, no external state
- Error simulation: Return any status code on demand
WireMock
WireMock is the most feature-rich mock server for HTTP. It supports request matching, response templating, stateful scenarios, fault injection, and recording/replay.
Quick Start
# Run WireMock standalone
docker run -it --rm -p 8080:8080 wiremock/wiremock
# Or with Java
java -jar wiremock-standalone.jar --port 8080
Stub Mappings
Stubs are JSON files that map request patterns to responses:
{
"request": {
"method": "GET",
"urlPattern": "/users/[0-9]+"
},
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"jsonBody": { "id": 42, "name": "Alice", "email": "[email protected]" }
}
}
Fault Injection
WireMock can simulate network-level faults, not just HTTP errors:
{
"request": { "method": "GET", "url": "/slow-endpoint" },
"response": {
"status": 503,
"fixedDelayMilliseconds": 5000,
"jsonBody": { "error": "Service temporarily unavailable" }
}
}
For connection-level faults:
{
"request": { "method": "POST", "url": "/payments" },
"response": {
"fault": "CONNECTION_RESET_BY_PEER"
}
}
Stateful Scenarios
WireMock scenarios let you model stateful interactions:
[
{
"scenarioName": "Order Flow",
"requiredScenarioState": "Started",
"newScenarioState": "OrderPlaced",
"request": { "method": "POST", "url": "/orders" },
"response": { "status": 201, "jsonBody": { "id": 1, "status": "pending" } }
},
{
"scenarioName": "Order Flow",
"requiredScenarioState": "OrderPlaced",
"request": { "method": "GET", "url": "/orders/1" },
"response": { "status": 200, "jsonBody": { "id": 1, "status": "pending" } }
}
]
WireMock in Python Tests
import pytest
import requests
from wiremock.client import WireMock, Mapping, Request, Response
@pytest.fixture(autouse=True)
def wiremock():
wm = WireMock(host='localhost', port=8080)
wm.reset()
yield wm
wm.reset()
def test_payment_gateway_failure(wiremock):
wiremock.stub(
Mapping(
request=Request(method='POST', url='/charge'),
response=Response(status=502, json_body={'error': 'Gateway error'})
)
)
result = payment_service.charge(amount=100)
assert result.success is False
assert result.retry_after is not None
Prism (OpenAPI Mock Server)
Prism by Stoplight generates a mock server directly from your OpenAPI specification. Any endpoint defined in the spec becomes immediately available with realistic responses.
# Install
npm install -g @stoplight/prism-cli
# Run mock server from local spec
prism mock openapi.yaml
# Run against a remote spec
prism mock https://api.example.com/openapi.yaml
Validation Mode
Prism can also act as a validation proxy — forwarding requests to your real API but validating both the request and response against the spec:
prism proxy openapi.yaml https://api.staging.example.com --errors
Any response from your API that doesn't match the spec returns a 500 with a validation error. This catches spec drift before it reaches clients.
Dynamic Responses
By default Prism returns examples defined in your OpenAPI spec. Use the -d flag for dynamically generated responses based on schemas:
prism mock openapi.yaml --dynamic
json-server
json-server creates a full REST API from a single JSON file with zero code:
npm install -g json-server
Create db.json:
{
"users": [
{ "id": 1, "name": "Alice", "email": "[email protected]" },
{ "id": 2, "name": "Bob", "email": "[email protected]" }
],
"orders": [
{ "id": 1, "userId": 1, "total": 4999, "status": "shipped" }
]
}
json-server --watch db.json --port 3001
# GET /users → 200 with array
# GET /users/1 → 200 with object
# POST /users → 201 with created object
# DELETE /users/1 → 200
Custom Status Codes in json-server
Add middleware to return custom status codes:
// middleware.js
module.exports = (req, res, next) => {
if (req.path === '/payments' && req.method === 'POST') {
return res.status(422).json({ error: 'Card declined', code: 'card_declined' });
}
next();
};
json-server --watch db.json --middlewares middleware.js
Custom Mock Servers
Sometimes you need more control than WireMock or Prism provide. A custom mock server in Express or FastAPI gives you full flexibility:
# FastAPI mock server
from fastapi import FastAPI, Response
from typing import Any
import random
app = FastAPI()
ERROR_RATE = 0.1 # 10% of requests return 500
@app.post('/payments/charge')
def charge_payment(body: dict[str, Any]) -> Response:
if random.random() < ERROR_RATE:
return Response(status_code=502, content='{"error": "Gateway timeout"}')
if body.get('amount', 0) > 100000:
return Response(status_code=422, content='{"error": "Amount exceeds limit"}')
return Response(
status_code=200,
content='{"charge_id": "ch_test_123", "status": "succeeded"}'
)
Recording Proxy
A recording proxy captures real API traffic and stores it for replay:
# WireMock in record mode
java -jar wiremock-standalone.jar --proxy-all https://api.stripe.com --record-mappings
After recording, stop the proxy and run in stub mode. All recorded interactions are replayed deterministically — no network required.
Choosing a Mock Tool
| Tool | Best For |
|---|---|
| WireMock | Complex matching, stateful scenarios, fault injection |
| Prism | OpenAPI-first teams, spec validation, zero-config mocking |
| json-server | Simple REST CRUD, rapid prototyping |
| Custom server | Precise control, probabilistic failures, custom logic |