When you're building a backend that integrates with multiple third-party services, such as marketplaces, payment gateways, shipping providers, and 3PLs, your test suite quickly becomes a hostage to availability, rate limits, and unpredictable responses. I hit this problem hard while working on an integration platform that connected to external partners.
In this article, I'll walk through what mock testing is, why it matters, and give a practical overview of three language-agnostic tools: Postman Mock Server, WireMock, and the Pact Stub Server.
This is the first article in a three-part series:
- Part 1 (this article): Language-agnostic tools — Postman, WireMock, and Pact Stub Server
- Part 2: PHP-specific tools — Phiremock, HTTP Mock, and Mock Webserver
- Part 3: Node.js-specific tools — Nock and MSW
The tools in this article don't care what language your application is written in. Whether you're running PHP, Node.js, Java, Python, or Go — these tools will work because they operate at the HTTP level, not at the code level. They're also the best choice when multiple services in different languages all need to share the same set of mocks.
What is Mock Testing?
Mock testing is the practice of emulating external services so your application can be tested against any scenario in complete isolation, without touching the real service.
The key advantages are that developers and testers get expected results without depending on a real (possibly unstable) external service, there's no risk of polluting production or staging data on third-party systems, you have full control over responses to simulate timeouts, 500 errors, malformed payloads, and edge cases, and tests run faster with no real network overhead.
The goal, in practical terms, is to intercept every outbound HTTP call your application makes and return a pre-defined, predictable response. Your code doesn't know the difference, and that's exactly the point.
Tools at a Glance
| Tool | Type | How it runs | Best for |
|---|---|---|---|
| Postman Mock Server | Cloud-based GUI | Cloud (no setup) | Prototyping, team sharing, frontend collaboration |
| WireMock | HTTP stub server | Docker / standalone JAR | Powerful integration test environments |
| Pact Stub Server | HTTP stub server | Binary / Docker | Serving pre-written pact JSON files as a live mock |
Postman Mock Server
If your team already uses Postman for API documentation and manual testing, spinning up a mock server from an existing collection is remarkably quick — and since it runs in the cloud, any service in any language can call it.
How to Create One
Step 1 — Add an example to a request.
Open a request inside a Collection, send it once to capture a real response, click the ... menu to the right of the request, and choose Add Example. Postman pre-fills it with the actual response you received.
Step 2 — Create the mock server.
Go to the Collection settings and click + New next to the Mock Servers section. Set the mock server name, pick an environment (e.g. API_LOCAL), optionally check Save the mock server URL as an environment variable, and click Create Mock Server.
Postman generates a unique URL like https://xxxxxxxx.mock.pstmn.io. Any request matching your saved examples will return the mocked response. In your application config, swap the base URL to point to the testing URL. No code changes needed.
Simulating Different Scenarios
You can add multiple examples to the same request, each with a different response body or status code. Postman selects the matching example based on query params, headers, or path.
For example, having two examples for GET /api/products/:id — one returning 200 with a product, another returning 404 with an error — lets different consumers test both paths just by hitting different URLs or passing different headers.
Pros & Cons
Pros: Zero configuration code, excellent GUI, language-independent (any service can call it), great for sharing mocks across teams, free tier available.
Cons: Responses must be captured from a real endpoint first — you can't fabricate a response that doesn't yet exist without manually editing the example body. Limited dynamic manipulation compared to code-based tools. Requires a Postman account.
WireMock
WireMock is the battle-tested standalone HTTP mock server originally from the Java ecosystem — but it's fully language-agnostic and works perfectly from any project. You run it as a Docker container or a standalone JAR, and interact with it via a REST API or one of its official client libraries for Node.js, Python, Go, and others.
It's the right choice when you need a powerful, long-lived mock server shared across multiple services in a Docker Compose environment, or when you need advanced features like stateful scenarios and response templating.
Starting WireMock via Docker
docker run -d \
--name wiremock \
-p 8080:8080 \
-v $PWD/wiremock:/home/wiremock \
wiremock/wiremock:latest
Once running, WireMock exposes http://localhost:8080 for your application to call, and http://localhost:8080/__admin for its management API.
Defining Stubs via the REST API
You can create stubs with a plain HTTP POST — any language, any tool:
curl -X POST http://localhost:8080/__admin/mappings \
-H "Content-Type: application/json" \
-d '{
"request": {
"method": "GET",
"url": "/api/products/1"
},
"response": {
"status": 200,
"body": "{\"id\": 1, \"name\": \"Gift Box\", \"price\": 49.99}",
"headers": { "Content-Type": "application/json" }
}
}'
Stub Files (Declarative Approach)
Alternatively, drop JSON files into ./wiremock/mappings/ and they are loaded on startup — great for version-controlling your mock definitions alongside your code:
{
"request": {
"method": "POST",
"urlPattern": "/api/orders",
"bodyPatterns": [
{
"matchesJsonPath": "$.productId"
}
],
"headers": {
"Authorization": { "matches": "Bearer .+" }
}
},
"response": {
"status": 201,
"jsonBody": { "orderId": "abc-123" },
"headers": { "Content-Type": "application/json" },
"fixedDelayMilliseconds": 200
}
}
Stateful Scenarios
One of WireMock's most powerful features is scenarios — mocks that change state based on how many times they've been called. This is perfect for simulating async workflows.
{
"scenarioName": "Order Processing",
"requiredScenarioState": "Started",
"newScenarioState": "Processing",
"request": {
"method": "POST",
"url": "/api/orders"
},
"response": {
"status": 202,
"jsonBody": { "status": "processing" }
}
}
{
"scenarioName": "Order Processing",
"requiredScenarioState": "Processing",
"request": {
"method": "GET",
"url": "/api/orders/abc-123"
},
"response": {
"status": 200,
"jsonBody": { "status": "completed" }
}
}
Response Templating
WireMock can also extract data from the request and inject it into the response — useful when your code echoes back submitted data:
{
"request": {
"method": "POST",
"url": "/api/products"
},
"response": {
"status": 201,
"body": "{\"id\": \"{{randomValue type='UUID'}}\", \"name\": \"{{{jsonPath request.body '$.name'}}}\"}",
"headers": { "Content-Type": "application/json" },
"transformers": ["response-template"]
}
}
Pros & Cons
Pros: Extremely feature-rich (stateful scenarios, response templating, fault simulation, proxying); runs in Docker so it's shareable across a whole team or CI environment; battle-tested at enterprise scale; excellent documentation.
Cons: Requires a running Docker container or a Java process — more infrastructure overhead than in-process solutions; initial learning curve with the mapping DSL; the stub format is verbose for simple cases.
Pact Stub Server
The Pact Stub Server is a standalone HTTP mock server that serves responses defined in pact-format JSON files. You write your interactions (request/response pairs) as JSON, point the stub server at them, and it starts serving those responses immediately — no code required, no language SDK to install.
It's built in Rust, distributed as a single binary or a Docker image, and fully language-agnostic. The stub server is conceptually similar to WireMock's declarative mapping files, but uses the Pact V4 specification JSON format instead.
Note: This is not the same as Pact contract testing. The stub server simply serves pre-written responses — there is no consumer/provider verification involved. Contract testing is a separate topic we'll cover in a future article.
Installation
Via Docker (recommended):
docker pull pactfoundation/pact-stub-server
Via binary download:
Download the latest release for your platform from the GitHub releases page (supports Linux, macOS, and Windows on both x86_64 and arm64).
Writing a Pact Stub File
The stub server reads standard pact JSON files. You can write these by hand — they're just JSON files describing which request should return which response:
{
"consumer": { "name": "ProductService" },
"provider": { "name": "ProductAPI" },
"interactions": [
{
"description": "get product 1",
"request": {
"method": "GET",
"path": "/api/products/1"
},
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": {
"id": 1,
"name": "Gift Box",
"price": 49.99
}
}
},
{
"description": "get unknown product",
"request": {
"method": "GET",
"path": "/api/products/999"
},
"response": {
"status": 404,
"headers": { "Content-Type": "application/json" },
"body": { "error": "Not found" }
}
},
{
"description": "create an order",
"request": {
"method": "POST",
"path": "/api/orders",
"headers": { "Content-Type": "application/json" }
},
"response": {
"status": 201,
"headers": { "Content-Type": "application/json" },
"body": { "orderId": "abc-123", "status": "processing" }
}
}
]
}
Save this as pacts/ProductService-ProductAPI.json.
Running the Stub Server
From a single pact file:
pact-stub-server --file pacts/ProductService-ProductAPI.json --port 8080
From an entire directory of pact files:
pact-stub-server --dir pacts/ --port 8080
Via Docker, mounting a local pacts folder:
docker run -t -p 8080:8080 \
-v "$(pwd)/pacts:/app/pacts" \
pactfoundation/pact-stub-server \
-p 8080 -d /app/pacts
Once running, your application points to http://localhost:8080 instead of the real service. The stub server matches incoming requests by method, path, and query parameters, then returns the matching response body and headers.
Docker Compose Integration
For a shared dev/test environment, add the stub server alongside your application services:
services:
app:
build: .
environment:
PRODUCT_API_URL: http://stub:8080
depends_on:
- stub
stub:
image: pactfoundation/pact-stub-server
ports:
- "8080:8080"
volumes:
- ./pacts:/app/pacts
command: ["-p", "8080", "-d", "/app/pacts"]
Serving Multiple Scenarios with Provider States
If your application needs the same endpoint to return different responses depending on the test scenario (e.g. an existing product vs. an out-of-stock one), use provider states. Each interaction can declare a providerState, and you filter which state the stub server uses at runtime.
Add multiple interactions for the same endpoint with different provider states in your pact file:
{
"consumer": { "name": "ProductService" },
"provider": { "name": "ProductAPI" },
"interactions": [
{
"description": "get product 1 - in stock",
"providerState": "product 1 is in stock",
"request": { "method": "GET", "path": "/api/products/1" },
"response": {
"status": 200,
"body": { "id": 1, "name": "Gift Box", "available": true }
}
},
{
"description": "get product 1 - out of stock",
"providerState": "product 1 is out of stock",
"request": { "method": "GET", "path": "/api/products/1" },
"response": {
"status": 200,
"body": { "id": 1, "name": "Gift Box", "available": false }
}
}
]
}
Start the stub server filtered to a specific state:
pact-stub-server --dir pacts/ --port 8080 --provider-state "in stock"
Or, if you need the server to handle multiple states in a single run, pass the desired state as a request header. Start the server with --provider-state-header-name:
pact-stub-server --dir pacts/ --port 8080 \
--provider-state-header-name "X-Provider-State"
Then your test sends the header to tell the stub which interaction to match:
# Returns the "in stock" response
curl -H "X-Provider-State: product 1 is in stock" http://localhost:8080/api/products/1
# Returns the "out of stock" response
curl -H "X-Provider-State: product 1 is out of stock" http://localhost:8080/api/products/1
Watch Mode
During development, pact files change frequently. The --watch flag tells the stub server to monitor files and reload automatically without a restart:
pact-stub-server --dir pacts/ --port 8080 --watch
Any time you add, edit, or delete a pact file in the watched directory, the stub server picks up the changes immediately.
Loading Pacts from a URL or Pact Broker
The stub server can also fetch pact files from remote sources, which is useful in CI environments:
# Load from a URL
pact-stub-server --url https://your-server/pacts/ProductService-ProductAPI.json --port 8080
# Load from a Pact Broker (authenticated)
pact-stub-server \
--broker-url https://your-pact-broker \
--token your-bearer-token \
--provider-name ProductAPI \
--port 8080
CORS Support
For browser-based consumers (e.g. a React app calling the stub during development), enable CORS with the -o flag:
pact-stub-server --dir pacts/ --port 8080 --cors
This automatically responds to OPTIONS preflight requests and adds the Access-Control-Allow-Origin: * header to all responses.
Pros & Cons
Pros: Single binary with zero runtime dependencies — no JVM, no Node.js, no PHP needed; language-agnostic (any service can call it); JSON stub files are plain text and version-control friendly; watch mode makes development smooth; can pull pacts directly from a Pact Broker in CI; multi-platform Docker image available.
Cons: Matching is limited to method, path, and query params — no body-matching or conditional response logic like WireMock offers; response templating (injecting request data into responses) is not supported; designed primarily for prototyping rather than complex integration test scenarios.
Summary
The three tools in this article sit at different points on the effort/power spectrum. Postman gets you a shareable mock in minutes with no code, making it ideal for early prototyping and frontend collaboration. WireMock gives you the full power of a production-grade stub server with stateful scenarios and response templating, making it the right choice for complex integration test environments. The Pact Stub Server sits between the two — a single binary driven entirely by plain JSON files, with no infrastructure overhead and zero language dependencies.
In practice, they're not mutually exclusive: Postman works great during early development when the API isn't stable yet, the Pact Stub Server is a lightweight option for dev and CI environments where you want file-based stub definitions, and WireMock handles scenarios that need more sophisticated matching or stateful behaviour.
This is Part 1 of a three-part series on mock servers. Continue with Part 2: PHP Tools and Part 3: Node.js Tools. (To be published soon)
Article based on an internal presentation I gave at a previous company. If you have questions or want to share how your team approaches this problem, drop a comment below!
— Bruno Souza | Senior Software Engineer | LinkedIn




Top comments (0)