← Recipes

阅读中文版本

Stub Third-Party API Failures to Prove Client Resilience

Spin up WireMock with fault scenarios for latency, 500s, and 429 throttling, point the client at it, and prove retries, timeouts, and fallbacks actually work.

~40 minapiresiliencetesting

Task

Simulate a third-party API returning errors and delays with WireMock and verify the client degrades gracefully instead of hanging or crashing.

When to use this

The payment provider had a 40-minute outage. The checkout service queued requests until its connection pool filled and the whole site went down, all because resilience paths were never exercised.

Prerequisites

  • WireMock standalone jar or Docker image available
  • A client whose base URL for the third party is configurable
  • A safe environment to run degraded-path tests

Steps

1. Start WireMock and record that it is empty.

docker run -d -p 8080:8080 --name wiremock wiremock/wiremock:3.9.1 && curl -s http://localhost:8080/__admin/mappings | head -c 200

Expected output: The admin endpoint responds with an empty mappings list, confirming the stub server is up.

2. Create a delay scenario that exceeds the client timeout.

curl -s -X POST http://localhost:8080/__admin/mappings -d '{"request":{"method":"POST","url":"/v1/charges"},"response":{"fixedDelayMilliseconds":30000,"status":200}'

Expected output: POST /v1/charges now stalls 30 seconds; a client with a 5-second timeout must abort instead of waiting.

3. Add a throttling scenario that demands backoff.

curl -s -X POST http://localhost:8080/__admin/mappings -d '{"request":{"method":"GET","url":"/v1/rates"},"response":{"status":429,"headers":{"Retry-After":"2"}}'

Expected output: GET /v1/rates always returns 429 with Retry-After, so the client must back off or fail fast rather than hot-looping.

4. Add a transient 500 that succeeds on the second call.

curl -s -X POST http://localhost:8080/__admin/mappings -d '{"scenarioName":"flaky","requiredScenarioState":"Started","newScenarioState":"Recovered","request":{"method":"GET","url":"/v1/quote"},"response":{"status":500}}'

Expected output: The first call returns 500 and the scenario state moves to Recovered; a retry on the same stub then succeeds.

5. Point the client at the stub and run its test suite.

PAYMENT_BASE_URL=http://localhost:8080 npm test -- --grep resilience

Expected output: The resilience tests pass only if the client aborts slow calls, honors Retry-After, and retries the transient 500.

Troubleshooting

Problem

Client ignores the injected 429

Solution

The retry policy keys on status 503 only; broaden the retryable set to include 429 and read Retry-After when present.

Problem

Client hangs the whole suite

Solution

The timeout is configured on the wrong layer, for example on connection but not on response; set an end-to-end request deadline such as AbortSignal.timeout.

Problem

Stub returns 404 for matched calls

Solution

The mapping URL or method differs from what the client sends, including a trailing slash; inspect the request journal under __admin/requests to see the exact unmatched request.

Limitations

  • Stubbed latency is synthetic and lacks the jitter and packet loss of real networks, so tune timeouts with headroom.
  • Scenario state resets on restart, so sequence-dependent tests must set up their scenario state explicitly.

Resilience that has never failed is just hope

Every client claims to retry and time out correctly, but those paths only run when the third party misbehaves, which is exactly when you cannot test. WireMock inverts this: it stands in for the provider and misbehaves on command, with fixed delays, error statuses, and scenario-based sequences. Point your client at it and resilience becomes a test you can run on every build instead of a paragraph in the design doc.

# One-off 429 with Retry-After
curl -s -X POST http://localhost:8080/__admin/mappings -d \
  '{"request":{"method":"GET","url":"/v1/rates"},"response":{"status":429,"headers":{"Retry-After":"2"}}'

Read the journal, not your assumptions

When a stub does not match, WireMock still records the incoming request in its journal under __admin/requests. Comparing the recorded URL, method, and headers with the mapping shows the mismatch in one diff, which is far faster than guessing at trailing slashes and header casing.

Test the failure, then the recovery

The interesting assertion is often not that the client survived the outage but that it recovered afterward: retry queues drained, caches refilled, and alerts cleared. Use scenario states to model a provider that fails twice and then heals, and assert on both the degraded behavior and the return to normal.