HTTP API Mocking — WireMock · MockServer · Prism · MSW
HTTP API Mocking — WireMock · MockServer · Prism · MSW
Some external SaaS providers have no emulator: Kakao Login · Naver Login · Toss Payments · DuckDNS · Slack Webhook · OpenAI · Gemini, plus services where the official side never built one, such as Firebase Cloud Messaging. When you want to cut these external dependencies, the simplest path is to bring up a mock server that imitates HTTP responses.
1. About mock servers
What a mock server does is simple:
- Receive an HTTP request.
- Find a matching stub by rules (URL · method · headers · body).
- Return the predefined response (status code · headers · body).
From the code's perspective, just changing the external SaaS base URL to the mock server is enough — the SDK, HTTP client, auth flow, and serialization are still verified end to end. The mock cannot reproduce domain-specific accuracy (inference quality), but call shape, integration flow, and failure paths are reproduced precisely.
2. Tool matrix
| Tool | Started | Form | License | Strengths | Limits |
|---|---|---|---|---|---|
| WireMock (Java) | 2011 | Standalone server (Docker) | Apache 2.0 | Rich matchers · scenarios · recording · admin API | JVM, ~150 MB |
| MockServer (Java) | 2014 | Standalone server (Docker) | Apache 2.0 | Almost on par with WireMock · richer callbacks | Sparse Korean references |
| Prism (Stoplight) | 2017 | Standalone server (Node) | Apache 2.0 | OpenAPI spec → auto mock | Depends on spec accuracy |
| MSW | 2018 | Browser/Node process | MIT | Inline definitions · natural fit for React tests | Cannot verify external processes |
Selection notes:
- One container shared across services → WireMock (most Korean references).
- Accurate OpenAPI spec for automation → Prism.
- Frontend unit tests only → MSW.
- Dynamic state-machine responses → WireMock Scenarios.
3. Bring up WireMock
services:
wiremock:
image: wiremock/wiremock:3.x
ports:
- "8089:8080"
command:
- --port=8080
- --global-response-templating
- --verbose
volumes:
- ./mappings:/home/wiremock/mappings:ro
- ./__files:/home/wiremock/__files:ro
All JSON files under mappings/ load at startup. Use __files/ for large response bodies in separate files.
4. Stub JSON
// mappings/kakao-login.json
{
"request": {
"method": "POST",
"urlPath": "/oauth/token"
},
"response": {
"status": 200,
"headers": { "Content-Type": "application/json;charset=UTF-8" },
"jsonBody": {
"access_token": "stub-{{randomValue length=16 type='ALPHANUMERIC'}}",
"token_type": "bearer",
"expires_in": 21599
},
"transformers": ["response-template"]
}
}
transformers: ["response-template"] is required for templates like {{randomValue ...}} to work.
5. Matcher · response keys
Matcher:
| Key | Meaning |
|---|---|
method |
GET · POST etc. |
url |
Exact match |
urlPath |
Match ignoring query |
urlPathPattern |
Regex (FCM's /v1/projects/[^/]+/messages:send) |
headers |
Authorization · Content-Type |
bodyPatterns |
Body JSON path · regex |
queryParameters |
Query parameters |
Response:
| Key | Meaning |
|---|---|
status |
HTTP status code |
headers |
Response headers |
jsonBody |
JSON body |
body |
Text body |
bodyFileName |
Reference a file under __files/ |
fixedDelayMilliseconds |
Artificial delay — timeout simulation |
fault |
Connection drop · malformed response — resilience tests |
6. Scenarios — state machines
When the same endpoint must respond differently across calls.
{
"scenarioName": "Token refresh",
"requiredScenarioState": "Started",
"newScenarioState": "RefreshNeeded"
}
Reproduces OAuth refresh, token expiry, and similar scenarios.
7. Verifying calls — the requests API
# Which requests came in
curl http://localhost:8089/__admin/requests
# Hit count of a specific stub
curl -X POST http://localhost:8089/__admin/requests/count \
-H 'Content-Type: application/json' \
-d '{"method":"POST","url":"/oauth/token"}'
# Unmatched requests — diagnose missing stubs
curl http://localhost:8089/__admin/requests/unmatched
In CI, assertions like "FCM was sent N times" or "Kakao login was never called" become possible. A mock server is not just a stub but a verifier — that is the core difference from a generic reverse proxy.
8. Recording — capturing real responses
docker run -p 8089:8080 wiremock/wiremock \
--proxy-all="https://kapi.kakao.com" --record-mappings
In this mode, calling the production endpoint through the code makes WireMock capture responses and drop them into mappings/ automatically. The fastest path when the exact response shape of an external API is unknown.
9. Limitations
A mock is just a mock — if the external service quietly changes its responses, the stub goes stale. Make a real call periodically.
Inference and computation accuracy cannot be reproduced — Gemini answers, OpenAI embedding semantics, payment-module PG branches are dummy stubs and miss domain regressions.
Auth flows can be inaccurate — JWT signature checks, OAuth refresh, and signature headers are bypassed when a stub returns a plain 200 OK. At least one production-environment pass is needed.
HTTPS certificates — WireMock supports HTTPS, but its private CA is untrusted by clients. For tests only, work around with rejectUnauthorized: false.
10. CI integration pattern
- run: docker compose up -d wiremock
- run: |
until curl -sf http://localhost:8089/__admin/health; do sleep 1; done
- run: pnpm test
- run: |
# Verify call counts
count=$(curl -s -X POST http://localhost:8089/__admin/requests/count \
-H 'Content-Type: application/json' \
-d '{"method":"POST","url":"/v1/projects/test/messages:send"}' \
| jq .count)
[[ "$count" -ge 1 ]] || { echo "FCM send call missing"; exit 1; }
Closing thoughts
The value of HTTP API mocks is verifying call shapes, integration flows, and failure paths — not domain accuracy. The natural shape is "use the emulator if there is one, mock if not" — Firebase Auth/Firestore for the emulator; FCM · Kakao · Naver · Gemini for mocks.
Next
- (end of cloud)
WireMock official · MockServer · Prism · MSW · WireMock standalone for reference.