API Mock Response Builder
Design fake JSON endpoints with custom status, headers & delays — test client error handling without a real backend.
Why Developers Burn Hours Waiting for Real APIs — And How Mock Responses Fix That
It starts with a Slack message: "Can you finish the error handling on the payment screen? The backend team says the 429 rate-limit endpoint will be ready next sprint." You stare at your partially-written retry logic, knowing you cannot test it without the actual server sending that response. So you either wait, hard-code a fake condition in a way you will forget to remove, or — worst of all — push code that has never actually handled a failure path.
This is not an edge case. It is the single most common bottleneck in frontend-backend parallel development. Backend teams move on sprint schedules. Frontend teams need to wire up every status code, every error shape, every slow-network scenario now, not next Tuesday. The gap between "API contract agreed" and "API actually exists" can be two weeks, four weeks, sometimes a whole quarter for large organizations.
Mock response builders exist to close that gap entirely.
What a Mock Response Actually Simulates
A naive approach is to just return a hardcoded JavaScript object when a flag is set. That works until you realize your fetch() error handling checks response.ok, which depends on the actual HTTP status code. Or your retry logic reads the Retry-After header. Or your auth interceptor looks for WWW-Authenticate. None of those exist in a fake JS object.
A properly structured mock simulates the full HTTP response surface:
- Status code — the numeric code that determines
response.ok, which redirect handling fires, and which error branch in your code runs. - Status text — "Unauthorized", "Too Many Requests", "Unprocessable Entity" — often displayed in error UI or logged for debugging.
- Headers —
Retry-After,X-RateLimit-Remaining,Locationon 201s,WWW-Authenticateon 401s. These are not decoration; production clients read them. - Body shape — the exact JSON structure your error parser expects, including field names, nesting, and error code strings.
- Timing — a 200ms response and a 4000ms response expose completely different UX bugs. Spinners, timeouts, skeleton screens, and abort controllers all behave differently under load.
The Hidden Cost of Skipping Delay Testing
Most developers mock the happy path and the 404. Almost nobody mocks a 3.5-second response until a production incident forces them to. The consequences are predictable: a loading spinner with no timeout, a button that stays disabled indefinitely, a progress bar that fills to 100% and freezes.
One real-world example: a fintech team built a loan application form that hit a credit bureau API. In staging, the API responded in 180ms. In production under load, it sometimes took 8 seconds. The submit button disabled itself on click and never re-enabled if the request timed out on the client. Users submitted three times, generating three applications. The fix was trivial — an AbortController with a timeout and a re-enable handler — but nobody had ever simulated a slow response during development.
Building artificial delay into your mock workflow forces you to confront these scenarios before users do.
Status Code Coverage That Actually Matters
A 200 is easy. The interesting cases are:
401 vs 403: These are not interchangeable. 401 means "authenticate first" — your client should redirect to login. 403 means "you are authenticated but not allowed" — showing a login redirect is confusing and wrong. Most client codebases handle both with the same handler and ship subtle UX bugs as a result.
422 Unprocessable Entity: This is the right code for validation failures, not 400. A 400 means the request was malformed (bad JSON, wrong content-type). A 422 means the structure was fine but the data violated business rules. If your API sends 422 with a structured details array per field, your frontend form logic needs to map those fields to inline error messages — a complex rendering path that breaks in non-obvious ways when untested.
429 with Retry-After: Rate limiting is trivial to handle correctly if you test it: read the Retry-After header, schedule a retry, update the UI to show a countdown. It is easy to handle incorrectly if you never receive an actual 429 with that header during development — your retry fires immediately, hammering the server in a loop.
204 No Content: Calling response.json() on a 204 throws a parse error. This crashes more apps than developers want to admit. Mock a 204 DELETE response and see if your fetch wrapper handles the empty body gracefully.
Using Mocks for Contract Testing
Beyond local development, mock responses serve a second purpose: verifying that both sides of an API contract agree on the shape. When you define a mock for a 422 response and the backend team's OpenAPI spec says the error field is called errors (plural) but your mock uses details (because that is what the backend developer described verbally last Tuesday), you have discovered a contract mismatch before either side shipped code.
This is the principle behind consumer-driven contract testing tools like Pact — the consumer (frontend) defines the responses it expects, and the provider (backend) runs those expectations against its actual implementation. A mock builder is the first step in that workflow: defining precisely what you are expecting so the expectation can be formalized.
The Service Worker Approach for Real Fetch Interception
The fetch snippet pattern — returning a fabricated Response object — works well in unit tests and component tests. For integration-level testing in a real browser, the cleaner approach is a service worker that intercepts fetch calls matching a URL pattern and returns the mock Response instead of hitting the network. Libraries like MSW (Mock Service Worker) formalize this, but the underlying mechanism is the same: construct a Response with the right status, headers, and body, and resolve the intercepted request with it.
The value of understanding the raw construction — new Response(body, { status: 429, headers }) — is that you know exactly what the mock is doing and can verify that it accurately represents what a real server sends. No magic.
Integrating Mock Responses into a Team Workflow
The most effective teams version-control their mock definitions alongside the frontend code. When the backend contract changes — say, a 422 response adds a new severity field per error — a developer updates the mock definition in the repository, and every team member who runs the dev environment immediately sees the new shape. This eliminates the "it worked on my machine" problem where one developer tested against the real staging API and others are still running stale mocks.
Mock definitions also serve as living documentation. A new developer joining the project can read the 401 mock response and immediately understand that the API returns an AUTH_001 error code, that the client should look for that code rather than relying solely on the status number, and that WWW-Authenticate is present. No wiki page required.
The Compound Benefit: Safer Error Paths in Production
Teams that routinely mock every status code they claim to handle ship measurably more resilient client code. The discipline of thinking through "what does a 503 response actually look like, and how should my app behave?" before writing the handler produces better handlers than thinking about it only when a 503 appears in production at 2am.
The mock builder is not just a development convenience. It is a forcing function that makes error path design explicit, testable, and reviewable before any backend infrastructure exists to produce those errors in the first place.