В процессе тестирования компонентов React часто возникает
необходимость имитировать сетевые запросы или внешние
зависимости с помощью моков. Одним из ключевых инструментов для этого
является msw (Mock Service Worker) или собственные
функции-заглушки. При этом важно организовать мок-обработчики так, чтобы
их можно было повторно использовать между тестами,
избегая дублирования кода и облегчая поддержку тестовой среды.
Обычно мок-обработчики оформляются как отдельные функции или массивы объектов, которые описывают поведение API:
import { rest } from 'msw';
export const handlers = [
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.status(200), ctx.json([{ id: 1, name: 'Alice' }]));
}),
rest.post('/api/login', (req, res, ctx) => {
const { username } = req.body;
return res(
ctx.status(200),
ctx.json({ token: `token-for-${username}` })
);
}),
];
Ключевой момент: такие handlers можно импортировать в любой тестовый файл, что позволяет централизованно управлять моками.
Чтобы использовать обработчики во всех тестах, создают отдельный файл для настройки сервера:
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
Далее в файле конфигурации тестов (setupTests.js или
аналогичном) подключают сервер:
import { server } from './testServer';
import '@testing-library/jest-dom';
// Запуск перед всеми тестами
beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }));
// Сброс обработчиков после каждого теста
afterEach(() => server.resetHandlers());
// Завершение работы сервера после всех тестов
afterAll(() => server.close());
Это обеспечивает чистое тестовое окружение: каждый тест получает свежие настройки моков без побочных эффектов от предыдущих вызовов.
Иногда требуется изменить поведение мока для конкретного теста, например, имитировать ошибку сервера:
import { rest } from 'msw';
import { server } from './testServer';
test('отображает сообщение об ошибке при неудачном запросе', async () => {
server.use(
rest.get('/api/users', (req, res, ctx) => {
return res(ctx.status(500));
})
);
render(<UserList />);
expect(await screen.findByText(/ошибка/i)).toBeInTheDocument();
});
Метод server.use() добавляет новые обработчики
для текущего теста, при этом глобальные handlers остаются
неизменными для других тестов. После теста afterEach
вызовет server.resetHandlers(), возвращая исходное
состояние.
Для удобства можно разделять глобальные handlers, которые всегда используются, и тестовые, специфичные для отдельных сценариев:
// handlers/globalHandlers.js
export const globalHandlers = [
rest.get('/api/users', (req, res, ctx) => res(ctx.json([{ id: 1, name: 'Alice' }])))
];
// handlers/testSpecificHandlers.js
export const errorHandlers = [
rest.get('/api/users', (req, res, ctx) => res(ctx.status(500)))
];
В тесте можно комбинировать их:
import { server } from './testServer';
import { errorHandlers } from './handlers/testSpecificHandlers';
beforeEach(() => {
server.use(...errorHandlers);
});
Такой подход упрощает поддержку больших проектов, где разные модули могут требовать разные варианты отклика API.
Если проект не использует msw, мок-обработчики можно
хранить как отдельные функции:
export const fetchUserMock = jest.fn(() =>
Promise.resolve([{ id: 1, name: 'Alice' }])
);
И использовать в разных тестах:
import { fetchUserMock } from './mocks';
beforeEach(() => {
fetchUserMock.mockClear();
});
test('рендерит список пользователей', async () => {
render(<UserList fetchUsers={fetchUserMock} />);
expect(await screen.findByText('Alice')).toBeInTheDocument();
});
Преимущество: одинаковые моки не нужно дублировать, можно централизованно управлять поведением и состоянием вызовов.
mocks/handlers.resetHandlers или mockClear), чтобы тесты
оставались изолированными.successHandlers,
errorHandlers, fetchUserMock.Такой подход к реиспользованию mock handlers обеспечивает масштабируемость тестов, предотвращает дублирование кода и упрощает управление различными сценариями API-запросов в проектах на React.