В React Testing Library основной целью является тестирование компонентов так, чтобы поведение приложения максимально отражало реальные условия работы. Чрезмерное использование моков нарушает этот принцип, создавая искусственную среду, в которой тест проверяет не поведение компонента, а структуру моков.
Ключевой момент: моки должны использоваться только для внешних зависимостей, которые невозможно или нежелательно подключать напрямую, таких как сетевые запросы, сложные сервисы или глобальные состояния, недоступные из теста. Локальные состояния, методы компонентов и стандартные хуки редко требуют мокирования.
При тестировании компонентов, выполняющих 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);
});
Только в случаях внешних зависимостей хук можно мокировать.
Чрезмерное мокирование превращает тест в документирование интерфейса зависимостей, а не проверку поведения. Минимизация моков сохраняет ценность тестов, делает их устойчивыми к рефакторингу и поддерживает уверенность в корректности приложения.