Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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 msw

MSW 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

src/mocks/handlers.ts
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 },
    );
  }),
];
src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

export const server = setupServer(...handlers);

Activating the server in test setup

src/test-setup.ts
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.