Динамические ответы сервера в тестах

При тестировании компонентов React, которые взаимодействуют с сервером, важно корректно моделировать динамические ответы. Компоненты могут отправлять HTTP-запросы и ожидать различные результаты: успешные ответы, ошибки или задержки. Неправильное тестирование таких сценариев приводит к нестабильным тестам и ложным срабатываниям.


Мокирование сетевых запросов

Для контроля серверных ответов в тестах используют мокирование. Наиболее популярные подходы включают:

  • jest.mock() для подмены модулей, выполняющих HTTP-запросы.
  • msw (Mock Service Worker) для имитации реальных сетевых вызовов на уровне Service Worker.
  • fetch-mock или axios-mock-adapter при использовании fetch или axios соответственно.

Пример с jest.mock() и fetch:

import { render, screen, waitFor } from '@testing-library/react';
import UserProfile from './UserProfile';

global.fetch = jest.fn();

test('отображает данные пользователя после запроса', async () => {
  fetch.mockResolvedValueOnce({
    ok: true,
    json: async () => ({ name: 'Иван', age: 30 }),
  });

  render(<UserProfile userId={1} />);

  const nameElement = await screen.findByText('Иван');
  expect(nameElement).toBeInTheDocument();
});

В этом примере ключевые моменты:

  • Используется mockResolvedValueOnce для задания конкретного ответа сервера.
  • Асинхронный поиск элемента через findByText учитывает задержку ответа.
  • Каждый тест может задавать разные ответы для одной и той же функции fetch.

Обработка ошибок сервера

Тестирование компонентов должно покрывать не только успешные ответы, но и ошибки: сетевые сбои, статус-коды 4xx и 5xx. Для этого создаются отдельные мок-ответы.

test('отображает сообщение об ошибке при сбое запроса', async () => {
  fetch.mockRejectedValueOnce(new Error('Сбой сети'));

  render(<UserProfile userId={1} />);

  const errorElement = await screen.findByText(/Ошибка/i);
  expect(errorElement).toBeInTheDocument();
});

Здесь важно:

  • Использовать mockRejectedValueOnce для симуляции исключений.
  • Проверять визуальное представление ошибки, чтобы убедиться, что компонент корректно реагирует на сбой.

Тестирование последовательных ответов

Иногда компонент делает несколько запросов подряд. Для проверки последовательных сценариев fetch позволяет настроить несколько ответов:

fetch
  .mockResolvedValueOnce({ ok: true, json: async () => ({ step: 1 }) })
  .mockResolvedValueOnce({ ok: true, json: async () => ({ step: 2 }) });

Это полезно для проверки:

  • Пошаговой загрузки данных.
  • Анимаций загрузки и индикаторов прогресса.
  • Состояний компонента при изменении данных.

Временные задержки и асинхронные эффекты

Реальные сетевые запросы имеют задержку, которую также можно эмулировать:

fetch.mockImplementationOnce(() =>
  new Promise(resolve => 
    setTimeout(() => resolve({ ok: true, json: async () => ({ name: 'Иван' }) }), 500)
  )
);
  • Использование setTimeout позволяет проверить индикаторы загрузки.
  • В React Testing Library для ожидания состояния используется waitFor или findBy* селекторы.
  • Следует избегать реальных таймеров в больших тестах; часто используется jest.useFakeTimers() для ускорения тестов.

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

MSW предоставляет более гибкий способ мокирования, позволяя создавать полноценный имитируемый сервер:

import { setupServer } from 'msw/node';
import { rest } from 'msw';

const server = setupServer(
  rest.get('/api/user/:id', (req, res, ctx) => {
    return res(ctx.json({ name: 'Иван', age: 30 }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Преимущества MSW:

  • Сценарии близки к реальной среде браузера.
  • Можно легко менять ответ для отдельных тестов через server.use().
  • Поддержка ошибок, задержек и разных статусов HTTP.

Тонкости асинхронного тестирования

При работе с динамическими ответами важно соблюдать правила:

  1. Ждать обновлений UI, а не просто проверять состояние после render. Используются findBy* и waitFor.
  2. Сбрасывать моки между тестами, чтобы избежать «утечек» состояний.
  3. Проверять все варианты ответа, включая ошибки, пустые данные и частично загруженные результаты.
  4. Минимизировать тестовую зависимость от времени, используя мок-таймеры или MSW.

Практические советы

  • Изолировать тесты от реального API. Любой внешний запрос делает тесты нестабильными.
  • Проверять не внутреннюю реализацию, а результат в DOM. React Testing Library ориентирована на тестирование поведения, а не методов.
  • Комбинировать статические и динамические данные. Для сложных форм и списков мокирование нескольких сценариев помогает покрыть edge-case.
  • Использовать типы и интерфейсы, если проект на TypeScript, для автоматической проверки структуры мок-данных.

Динамические ответы сервера требуют внимательного подхода к мокированию, асинхронному ожиданию и проверке различных сценариев. Корректная организация таких тестов делает их надежными, быстрыми и легко поддерживаемыми.