Mocking the API in React Tests
Mocking the API in React Tests
~16 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
Chapter 95 only tested the form's INTERACTION – AS SOON AS a test needs to verify the RESULT of a mutation, it needs a MOCK API, instead of running AGAINST the REAL backend.
Installing Mock Service Worker
npm install --save-dev mswMSW intercepts axios requests at the NETWORK level (instead of mocking axios ITSELF) – the PRODUCTION code (apiClient from chapter 74) stays UNCHANGED, EXACTLY the approach that keeps tests CLOSEST to REAL behavior.
Defining handlers
import { http, HttpResponse } from 'msw';
export const handlers = [
http.post('*/projects', async ({ request }) => {
const body = (await request.json()) as { name: string };
return HttpResponse.json(
{ '@id': '/api/projects/999', id: 999, name: body.name },
{ status: 201 },
);
}),
];import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);Activating the server in test setup
import '@testing-library/jest-dom';
import { beforeAll, afterEach, afterAll } from 'vitest';
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());resetHandlers() AFTER EVERY test MATTERS: WITHOUT this reset, a test-SPECIFIC handler (e.g. for a simulated error case) could ACCIDENTALLY AFFECT a SUBSEQUENT test.
A complete mutation test
it('clears the form after successfully creating', async () => {
const user = userEvent.setup();
renderWithQueryClient(<CreateProjectForm />);
const input = screen.getByPlaceholderText('Project name');
await user.type(input, 'Test project');
await user.click(screen.getByRole('button', { name: /create project/i }));
await waitFor(() => expect(input).toHaveValue(''));
});waitFor is NECESSARY since the mutation call is ASYNCHRONOUS – the test WAITS UNTIL the condition IS MET, instead of checking IMMEDIATELY (and thus TOO EARLY).
Simulating an error case
it('shows an error message on a validation error', async () => {
server.use(
http.post('*/projects', () =>
HttpResponse.json(
{ violations: [{ propertyPath: 'name', message: 'Too short.' }] },
{ status: 422 },
),
),
);
// ... fill out and submit the form, then check:
expect(await screen.findByText('Too short.')).toBeInTheDocument();
});server.use() OVERRIDES the DEFAULT handler ONLY for THIS ONE test – EXACTLY the violations mechanism from chapter 79 gets verified AUTOMATICALLY HERE, instead of MANUALLY in the browser.
Tipp: MSW handlers CAN ALSO be reused for the browser DevTools during development ("MSW in the browser") – USEFUL for developing the frontend WITHOUT a running backend, a topic BEYOND THIS course, but WORTH mentioning for FURTHER research.