Мокирование встроенных API: localStorage, fetch

В процессе тестирования компонентов React часто возникает необходимость взаимодействовать с браузерными встроенными API. Два самых распространённых примера — это localStorage и fetch. Для написания надёжных и изолированных тестов важно правильно их мокировать, чтобы избежать зависимости от состояния браузера или сетевых запросов.


Мокирование localStorage

localStorage используется для хранения данных на стороне клиента. В тестах прямое обращение к нему может приводить к непредсказуемым результатам, поэтому применяют мокирование.

Основные методы localStorage

  • getItem(key) — возвращает значение по ключу.
  • setItem(key, value) — сохраняет значение по ключу.
  • removeItem(key) — удаляет элемент.
  • clear() — очищает весь storage.

Пример мокирования localStorage в Jest

beforeEach(() => {
  const localStorageMock = (() => {
    let store = {};
    return {
      getItem: (key) => store[key] || null,
      setItem: (key, value) => {
        store[key] = value.toString();
      },
      removeItem: (key) => {
        delete store[key];
      },
      clear: () => {
        store = {};
      },
    };
  })();

  Object.defineProperty(window, 'localStorage', {
    value: localStorageMock,
  });
});

test('сохраняет данные в localStorage', () => {
  localStorage.setItem('token', '12345');
  expect(localStorage.getItem('token')).toBe('12345');
});

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

  • Создаётся локальный объект store для хранения значений.
  • Методы getItem и setItem эмулируют стандартное поведение localStorage.
  • Object.defineProperty используется для переопределения глобального window.localStorage.

Мокирование в отдельных тестах

Иногда требуется мокировать localStorage только для одного теста:

test('удаляет элемент из localStorage', () => {
  const removeItemMock = jest.fn();
  Object.defineProperty(window, 'localStorage', {
    value: { removeItem: removeItemMock },
  });

  localStorage.removeItem('key');
  expect(removeItemMock).toHaveBeenCalledWith('key');
});

Такой подход удобен для проверки вызовов без фактического хранения данных.


Мокирование fetch

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

Использование Jest для мокирования fetch

В Jest можно заменить fetch на мок-функцию:

beforeEach(() => {
  global.fetch = jest.fn();
});

afterEach(() => {
  jest.resetAllMocks();
});

Пример мокирования успешного ответа

test('получает данные с API', async () => {
  const fakeData = { id: 1, name: 'Test' };
  fetch.mockResolvedValue({
    ok: true,
    json: async () => fakeData,
  });

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

  expect(fetch).toHaveBeenCalledWith('/api/data');
  expect(data).toEqual(fakeData);
});

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

  • mockResolvedValue возвращает объект, имитирующий успешный Response.
  • Метод json также мокируется для возврата нужных данных.
  • Можно проверять вызовы fetch с помощью toHaveBeenCalledWith.

Пример мокирования ошибки

test('обрабатывает ошибку fetch', async () => {
  fetch.mockRejectedValue(new Error('Network error'));

  await expect(fetch('/api/data')).rejects.toThrow('Network error');
});

Такой подход позволяет тестировать обработку ошибок без реальных сетевых сбоев.


Мокирование fetch с использованием библиотеки msw

Для сложных сценариев, когда требуется более реалистичная имитация сетевых запросов, используется библиотека Mock Service Worker (MSW).

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

const server = setupServer(
  rest.get('/api/data', (req, res, ctx) => {
    return res(ctx.json({ id: 1, name: 'Test' }));
  })
);

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

test('получает данные через MSW', async () => {
  const response = await fetch('/api/data');
  const data = await response.json();

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

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

  • Возможность имитации различных эндпоинтов и состояний ответа.
  • Тесты остаются изолированными и повторяемыми.
  • Более точная имитация поведения настоящего сервера.

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

  1. Изолированность тестов: каждый тест должен работать независимо, без зависимости от предыдущего состояния localStorage или реальных сетевых запросов.
  2. Сброс моков после теста: использовать jest.resetAllMocks() или jest.clearAllMocks() для предотвращения влияния одного теста на другой.
  3. Выбор подхода: для единичных и простых тестов достаточно jest.fn(), для интеграционных — msw даёт более реалистичное поведение.
  4. Проверка вызовов: важно проверять, с какими аргументами вызывались методы, чтобы тесты проверяли не только результат, но и взаимодействие с API.

Мокирование встроенных API — ключевой элемент качественного тестирования React-приложений, позволяющий создавать быстрые, надёжные и воспроизводимые тесты без зависимости от браузерного состояния или сети.