Избежание чрезмерного мокирования

В React Testing Library основной целью является тестирование компонентов так, чтобы поведение приложения максимально отражало реальные условия работы. Чрезмерное использование моков нарушает этот принцип, создавая искусственную среду, в которой тест проверяет не поведение компонента, а структуру моков.

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

Сетевые запросы и API

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

Пример:

// api.js
export const fetchUser = async (id) => {
  const response = await fetch(`/users/${id}`);
  return response.json();
};

// MyComponent.js
import { fetchUser } from './api';

В тесте:

import { render, screen, waitFor } from '@testing-library/react';
import MyComponent from './MyComponent';
import * as api from './api';

jest.spyOn(api, 'fetchUser').mockResolvedValue({ id: 1, name: 'Alice' });

test('отображает имя пользователя', async () => {
  render(<MyComponent userId={1} />);
  await waitFor(() => expect(screen.getByText('Alice')).toBeInTheDocument());
});

Вывод: мокирование ограничивается только fetchUser, а сам компонент тестируется в реалистичных условиях. Полное мокирование слоя состояния или внутренних методов приведёт к утрате ценности теста.

Контекст и провайдеры

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

Пример:

import { render, screen } from '@testing-library/react';
import { ThemeProvider } from './ThemeContext';
import Button from './Button';

test('кнопка использует тему', () => {
  render(
    <ThemeProvider value={{ color: 'red' }}>
      <Button />
    </ThemeProvider>
  );
  expect(screen.getByRole('button')).toHaveStyle({ color: 'red' });
});

Мокирование ThemeProvider через Jest не нужно — тест проверяет реальное поведение компонента с контекстом.

Управление состоянием

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

import { render, screen } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
import Counter from './Counter';

const store = configureStore({ reducer: { counter: counterReducer } });

test('увеличение счетчика', () => {
  render(
    <Provider store={store}>
      <Counter />
    </Provider>
  );
  // действия и проверки
});

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

Пользовательские хуки

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

import { renderHook, act } from '@testing-library/react-hooks';
import useCounter from './useCounter';

test('useCounter увеличивает значение', () => {
  const { result } = renderHook(() => useCounter());
  act(() => result.current.increment());
  expect(result.current.value).toBe(1);
});

Только в случаях внешних зависимостей хук можно мокировать.

Практика “меньше моков — ближе к реальности”

  1. Не мокировать локальные функции компонента.
  2. Мокировать только внешние сервисы или API.
  3. Сохранять контексты и провайдеры реальными.
  4. Проверять реальные побочные эффекты, а не только вызовы функций.
  5. Выбирать тестовые данные, минимально отличающиеся от реальных, чтобы сохранить правдоподобие поведения.

Чрезмерное мокирование превращает тест в документирование интерфейса зависимостей, а не проверку поведения. Минимизация моков сохраняет ценность тестов, делает их устойчивыми к рефакторингу и поддерживает уверенность в корректности приложения.