How to Mock an API Before the Backend Exists
Turn one user journey into a reviewable interface and a working frontend without hiding unresolved backend decisions.
Key takeaways
- Agree on request and response behavior before treating a fixture as the intended API.
- Keep normal application request code active while a mock supplies controlled responses.
- Include empty, error, pending, and awkward-content states alongside success.
- Mocked tests need provider verification and selected real integrations once the backend exists.
You can mock an API before its backend exists by defining the intended requests and responses, supplying those responses through a mock, and building the client against that interface.
The important step is agreement. A frontend that works against invented data may still need extensive changes when the real service arrives.
Consider an order-history page. Start with one operation: fetch the signed-in customer’s orders and display either a list, an empty state, or an error.
1. Define the Operation Before Writing Fixtures
For this example, agree on the following behavior:
| Detail | Proposed contract |
|---|---|
| Request | GET to the application’s orders endpoint |
| Successful response | HTTP 200 with an items array |
| No orders | HTTP 200 with an empty items array |
| Signed-out request | HTTP 401 with a stable error code |
| Temporary service failure | HTTP 503 with a retryable error response |
| Order identifier | String |
| Tracking link | Absolute URL or null |
Also decide pagination, supported status values, and how identity reaches the service. A fixture should not silently decide those rules.
A reviewed description can be enough to begin. As the interface grows, a machine-readable schema can help validate examples and document constraints.
2. Create a Useful Success Fixture
Use content that exercises the layout and the client’s assumptions:
{
"items": [
{
"id": "ord_42",
"label": "Replacement filter set for the upstairs ventilation unit",
"status": "processing",
"trackingUrl": null
}
]
}
The long label makes wrapping visible. The null tracking link forces the client to handle an order that has not shipped.
Keep example values consistent with the agreed contract. If the backend will return timestamps with offsets, a fixture containing only an informal date string teaches the client the wrong behavior.
3. Intercept Requests Where It Fits the Workflow
A standalone mock server gives several clients a real HTTP endpoint. Browser-test interception is convenient when a test needs a controlled response without starting that server.
Here is a complete illustrative Playwright test. It assumes a frontend running at the local URL, a page that requests the orders endpoint, and the specified empty-state text.
import { test, expect } from '@playwright/test';
test('shows an empty order history', async ({ page }) => {
await page.route('http://localhost:3000/api/orders', async route => {
if (route.request().method() !== 'GET') {
throw new Error('Expected GET for order history');
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ items: [] }),
});
});
await page.goto('http://localhost:3000/orders');
await expect(page.getByText('No orders yet', { exact: true }))
.toBeVisible();
});
Register interception before navigation so the initial request is covered. Playwright’s mocking documentation explains this approach.
This test checks the frontend against a supplied response. It does not prove the real orders service returns that response.
4. Make Scenarios Explicit
Create named scenarios that are easy to select and reproduce:
- Orders available, including long labels and missing optional values.
- No orders.
- Session expired.
- Service unavailable.
- Response deliberately held pending before completion.
A pending response helps expose loading behavior. A network failure differs from an HTTP error response; include both when the client has separate handling.
Prefer controlled release of a pending response when checking loading state. A fixed delay can expire before an assertion on a slow runner.
Reset scenario state between tests. Otherwise one test’s simulated outage may leak into another test’s success path.
5. Keep Mock Configuration Outside Business Logic
The application should keep constructing normal requests. Avoid components that return fixtures directly when a special development flag is set.
With a standalone mock, use environment configuration or a development proxy to choose the destination. With interception, request code can remain unchanged while the test supplies the response.
A browser can impose cross-origin requirements when a mock runs on another port. Successful interception does not validate the eventual service’s CORS configuration.
Use synthetic data and keep development-only mocking out of the deployed production path.
6. Review the Contract With the Backend Work
Version fixtures and handlers alongside the interface agreement. Review changes to nullability, status values, and errors together.
Validation against a schema can catch a malformed fixture, but a valid fixture can still disagree with the implementation. Contract testing and integration testing address different parts of that gap.
If pagination remains undecided, say so in the design work. An unpaginated example should not quietly become a commitment to returning every order forever.
7. Connect the Finished Service
Once the backend is available, run selected client flows against it with controlled test accounts and records.
Verify authentication, serialization, actual empty responses, and the meaning of error codes. Check that the frontend’s request reaches the intended route and that permitted data belongs to the correct customer.
Keep mocks for reproducible UI scenarios and difficult failure cases. Their lasting value is control over the client environment; the real-service checks establish whether the implemented dependency fulfills the agreement.
An early mock is most useful when it makes interface disagreements visible before either side commits to them.
More Articles Like This

What Is a Mock Server? How Simulated APIs Work

Contract Testing vs Integration Testing: Differences and Examples

What Is Playwright? Browser Testing Explained With an Example
