API mocking lets frontend tests reproduce success, validation errors, authorization failures, delays, and server failures without depending on a live backend. A good mock behaves like the real API contract rather than simply returning whatever makes the test pass.
Why API mocking is needed
Real APIs make tests slow, flaky, environment-dependent, and difficult to control. Mocking lets a test deliberately reproduce situations that are difficult to trigger on demand.
Mock fetch
global.fetch = vi.fn();
fetch.mockResolvedValue({
ok: true,
json: async () => ({
items: [{ id: 1, name: "Laptop" }]
})
});
Mock Axios
vi.mock("axios");
axios.get.mockResolvedValue({
data: {
items: [{ id: 1, name: "Laptop" }]
}
});
Mock success and failure
api.get
.mockResolvedValueOnce({
data: { items: [] }
})
.mockRejectedValueOnce(
new Error("Network error")
);
MSW approach
Mock Service Worker is useful for larger test suites because it mocks requests at the network boundary. The application still performs normal HTTP requests, while the test server controls the response.
http.get("/api/products", () => {
return HttpResponse.json({
items: [
{ id: 1, name: "Laptop" }
]
});
});
Testing 401
server.use(
http.get("/api/profile", () => {
return new HttpResponse(null, {
status: 401
});
})
);
render(<Profile />);
expect(
await screen.findByText(/sign in again/i)
).toBeInTheDocument();
Testing 500
server.use(
http.get("/api/products", () => {
return new HttpResponse(null, {
status: 500
});
})
);
expect(
await screen.findByText(/unable to load/i)
).toBeInTheDocument();
Testing slow API
http.get("/api/products", async () => {
await delay(1000);
return HttpResponse.json({
items: []
});
});This allows the test to verify loading indicators and disabled buttons.
Mock contract example
GET /api/products
Success:
200
{
"items": [...]
}
Unauthorized:
401
Server failure:
500
Output
Scenario: successful API
UI: Laptop displayed
Scenario: 401
UI: Please sign in again
Scenario: 500
UI: Unable to load products
Scenario: slow response
UI: Loading products...
Common mistakes
- Mocking the function instead of the HTTP boundary for every integration test.
- Returning unrealistic data.
- Not testing failure responses.
- Allowing tests to leak mock state into other tests.
- Testing only 200 responses.
Interview questions
- Why mock APIs?
- Fetch mock vs MSW?
- How would you test 401 and 500?
- How do you test a loading state caused by a slow API?