Использование MSW (Mock Service Worker)

Mock Service Worker (MSW) — это инструмент, предназначенный для перехвата HTTP-запросов в тестах и замены их мока (заглушками). В отличие от традиционного мокирования с помощью библиотек вроде jest.mock(), MSW использует сервис-воркеры браузера для того, чтобы перехватывать запросы на уровне сетевого слоя. Это позволяет более реалистично тестировать взаимодействие с API, не затрагивая реальные сетевые ресурсы.

Зачем использовать MSW

Использование MSW предоставляет несколько значительных преимуществ при тестировании API-запросов:

  1. Мокирование реальных запросов: MSW перехватывает реальные HTTP-запросы, отправленные приложением, и заменяет их моками. Это позволяет тестировать, как приложение реагирует на различные ответы от сервера (например, успешные, ошибочные или тайм-ауты), не выполняя реальные запросы в сеть.
  2. Отсутствие зависимости от внешних API: В процессе тестирования не нужно полагаться на доступность и стабильность внешних сервисов, что позволяет избежать непредсказуемых ситуаций.
  3. Повторяемость тестов: Тесты становятся предсказуемыми и независимыми от состояния внешних сервисов, что упрощает поддержку и улучшает их стабильность.

Установка и настройка MSW

Для начала нужно установить библиотеку MSW, если она ещё не установлена:

npm install msw --save-dev

После этого необходимо настроить сервис-воркера и использовать его в тестах.

Создание обработчиков

В MSW обработчики (handlers) — это функции, которые перехватывают запросы и возвращают заранее заданные ответы. Каждый обработчик зависит от типа HTTP-запроса (например, GET, POST, PUT, DELETE) и URL-адреса, на который он должен реагировать.

Пример создания обработчика для GET запроса:

import { rest } from 'msw';

export const handlers = [
  rest.get('https://api.example.com/data', (req, res, ctx) => {
    return res(
      ctx.status(200),
      ctx.json({ message: 'Успешный запрос' })
    );
  }),
];

В этом примере запрос к API https://api.example.com/data будет перехвачен и возвращен ответ с кодом статуса 200 и телом ответа в формате JSON.

Настройка сервис-воркера в тестах

После того как обработчики созданы, необходимо активировать сервис-воркер. Это делается с помощью функции setupWorker, которая используется для перехвата запросов в браузере.

import { setupWorker } from 'msw';
import { handlers } from './handlers';

const worker = setupWorker(...handlers);

beforeAll(() => worker.start());
afterAll(() => worker.stop());

Этот код запускает сервис-воркер перед началом всех тестов и останавливает его после их выполнения. Важно, чтобы запуск сервис-воркера происходил только один раз в начале тестов, иначе могут возникнуть проблемы с дублированием запросов.

Интеграция с Jest

MSW интегрируется с Jest через глобальные хуки для настройки и очистки сервис-воркера.

  1. beforeAll: Запуск MSW перед выполнением тестов.
  2. afterAll: Остановка MSW после выполнения тестов.
  3. beforeEach: Можно использовать для настройки моков перед каждым тестом, если требуется изменение состояния.

Пример полного теста с использованием MSW:

import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';
import { setupWorker } from 'msw';
import { handlers } from './handlers';

const worker = setupWorker(...handlers);

beforeAll(() => worker.start());
afterAll(() => worker.stop());

test('Проверка успешного получения данных с API', async () => {
  render(<MyComponent />);

  const button = screen.getByText(/Загрузить данные/i);
  fireEvent.click(button);

  const message = await screen.findByText(/Успешный запрос/i);
  expect(message).toBeInTheDocument();
});

Работа с различными типами запросов

MSW позволяет работать с различными HTTP-методами, такими как GET, POST, PUT, DELETE. Обработчики можно настраивать для каждого типа запроса в зависимости от нужд теста.

Пример POST запроса:

rest.post('https://api.example.com/submit', (req, res, ctx) => {
  const { name } = req.body;
  return res(
    ctx.status(201),
    ctx.json({ message: `Пользователь ${name} создан` })
  );
}),

Этот обработчик перехватывает запросы POST и возвращает ответ с статусом 201 и сообщением о создании пользователя.

Использование параметров в URL

Можно использовать параметры в URL, чтобы мокировать динамические запросы.

rest.get('https://api.example.com/users/:id', (req, res, ctx) => {
  const { id } = req.params;
  return res(
    ctx.status(200),
    ctx.json({ userId: id, name: 'Иван Иванов' })
  );
}),

В этом примере обработчик будет перехватывать запросы с динамическим параметром id и возвращать информацию о пользователе.

Отладка и вывод ошибок

MSW имеет встроенную систему логирования, которая полезна для отладки. Чтобы включить логирование, нужно передать параметр logLevel при старте сервис-воркера:

const worker = setupWorker(...handlers);

worker.start({ logLevel: 'debug' });

С помощью логов можно следить за тем, какие запросы перехватываются, а также за тем, какие ответы отправляются.

Тестирование с использованием сценариев с ошибками

MSW также позволяет тестировать обработку ошибок, что важно для проверки стабильности приложения в случае проблем с API.

Пример обработчика для имитации ошибки:

rest.get('https://api.example.com/data', (req, res, ctx) => {
  return res(
    ctx.status(500),
    ctx.json({ error: 'Внутренняя ошибка сервера' })
  );
}),

С помощью этого обработчика можно смоделировать ошибку сервера и проверить, как приложение реагирует на такие ситуации.

Важные особенности MSW

  1. Реалистичность: MSW позволяет мокировать запросы на уровне сетевого слоя, что делает тесты более приближенными к реальному поведению приложения.
  2. Скорость: Мокирование запросов с помощью MSW быстрее, чем выполнение реальных запросов в сеть.
  3. Интерактивность: MSW позволяет проверять не только состояние приложения после выполнения запроса, но и как оно реагирует на различные сетевые ошибки, такие как тайм-ауты или ошибки сервера.

Использование MSW помогает создать более стабильные и предсказуемые тесты для приложений, взаимодействующих с внешними API, позволяя тестировать различные сценарии без зависимости от состояния и доступности реальных сервисов.