jest-fetch-mock для fetch API

При тестировании компонентов React, которые взаимодействуют с внешними API через fetch, важно изолировать логику приложения от реальных сетевых запросов. Библиотека jest-fetch-mock позволяет полностью контролировать ответы fetch и создавать надежные тесты без зависимости от сервера.


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

Для начала необходимо установить пакет:

npm install --save-dev jest-fetch-mock

После установки нужно подключить его в конфигурации Jest. Наиболее удобный способ — использовать файл setupTests.js:

import fetchMock from 'jest-fetch-mock';

fetchMock.enableMocks();

Опция enableMocks() заменяет глобальный fetch на мок-реализацию, доступную во всех тестах.

Важно убедиться, что Jest действительно загружает setupTests.js. В package.json это делается через поле:

"jest": {
  "setupFilesAfterEnv": ["<rootDir>/src/setupTests.js"]
}

Базовое использование

После настройки fetch становится мок-функцией, которую можно конфигурировать:

beforeEach(() => {
  fetch.resetMocks();
});

test('возвращает данные от API', async () => {
  fetch.mockResponseOnce(JSON.stringify({ id: 1, name: 'Test' }));

  const response = await fetch('/api/data');
  const data = await response.json();

  expect(data).toEqual({ id: 1, name: 'Test' });
  expect(fetch).toHaveBeenCalledWith('/api/data');
});

Ключевые моменты:

  • fetch.resetMocks() очищает предыдущие моки, что предотвращает «утечки» между тестами.
  • mockResponseOnce позволяет задать конкретный ответ на один вызов fetch.
  • Проверка вызова fetch с помощью toHaveBeenCalledWith гарантирует, что запрос был отправлен корректно.

Моки с ошибками

Для тестирования обработки ошибок API используется mockRejectOnce:

test('обрабатывает ошибку API', async () => {
  fetch.mockRejectOnce(new Error('Сетевая ошибка'));

  try {
    await fetch('/api/data');
  } catch (error) {
    expect(error.message).toBe('Сетевая ошибка');
  }

  expect(fetch).toHaveBeenCalledTimes(1);
});

Это полезно для проверки логики компонента при сбоях сети или некорректных данных.


Настройка глобальных ответов

Можно задать стандартный ответ для всех вызовов fetch, если один тест покрывает несколько запросов:

fetch.mockResponse(JSON.stringify({ success: true }));

test('несколько запросов', async () => {
  const response1 = await fetch('/api/one');
  const data1 = await response1.json();

  const response2 = await fetch('/api/two');
  const data2 = await response2.json();

  expect(data1).toEqual({ success: true });
  expect(data2).toEqual({ success: true });
});

Разница между mockResponseOnce и mockResponse:

  • mockResponseOnce — влияет только на один вызов.
  • mockResponse — задает поведение для всех вызовов до следующей очистки мока.

Моки с разными статусами HTTP

Для тестирования компонентов, реагирующих на разные коды ответа, используется объект с опциями:

fetch.mockResponseOnce(JSON.stringify({ message: 'Not found' }), { status: 404 });

const response = await fetch('/api/missing');

expect(response.status).toBe(404);
const data = await response.json();
expect(data).toEqual({ message: 'Not found' });

Таким образом, можно проверять обработку ошибок на уровне HTTP.


Комбинация с React Testing Library

В React Testing Library часто тестируют асинхронное поведение компонентов после запроса fetch:

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

test('отображает данные пользователя', async () => {
  fetch.mockResponseOnce(JSON.stringify({ id: 1, name: 'Alice' }));

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

  await waitFor(() => expect(screen.getByText('Alice')).toBeInTheDocument());
});

Особенности:

  • waitFor позволяет дождаться обновления DOM после асинхронного запроса.
  • fetch.mockResponseOnce гарантирует, что компонент получит предсказуемые данные.

Использование динамических моков

Можно задать функцию вместо статического ответа для имитации разных данных в зависимости от URL или метода:

fetch.mockResponse(req => {
  if (req.url.endsWith('/users')) {
    return Promise.resolve(JSON.stringify([{ id: 1, name: 'Alice' }]));
  }
  return Promise.resolve(JSON.stringify({ message: 'Not found' }));
});

const response = await fetch('/users');
const data = await response.json();
expect(data).toEqual([{ id: 1, name: 'Alice' }]);

Такой подход полезен для сложных сценариев с несколькими endpoint’ами.


Полезные методы jest-fetch-mock

  • mockResponseOnce(body, options) — один ответ на один вызов.
  • mockResponse(body, options) — одинаковый ответ для всех вызовов.
  • mockRejectOnce(error) — отклоняет один вызов с ошибкой.
  • mockReject(error) — отклоняет все последующие вызовы.
  • resetMocks() — очищает состояние всех моков, включая вызовы и ответы.
  • enableMocks() — подключает мок вместо глобального fetch.

Эти методы позволяют создавать детализированные тесты, полностью контролируя сетевое поведение компонентов React.


Рекомендации по использованию

  • Всегда сбрасывать моки перед каждым тестом (beforeEach(fetch.resetMocks)).
  • Использовать mockResponseOnce для отдельных тестов, чтобы исключить перекрестное влияние.
  • Для проверки компонентов с асинхронной логикой комбинировать jest-fetch-mock с waitFor или findBy из React Testing Library.
  • При необходимости проверять URL и параметры запроса с помощью toHaveBeenCalledWith.

Эта практика обеспечивает стабильные и предсказуемые тесты, полностью изолированные от внешних API и сети.