Тестирование компонентов в React почти всегда упирается в работу с зависимостями: API-клиентами, утилитами, хранилищами состояния, сторонними библиотеками. Управление такими зависимостями осуществляется через модули mocks, а их корректная очистка — критически важная часть надёжной тестовой среды.
React Testing Library отвечает за рендеринг и взаимодействие с компонентами, но не занимается подменой зависимостей. Эту задачу берёт на себя тестовый раннер, чаще всего Jest. Именно модульные mocks Jest используются для изоляции компонентов.
jest.mock('../api/userService', () => ({
getUser: jest.fn(),
}));
Такой mock полностью подменяет импортируемый модуль во всех тестах файла. Компонент при этом ничего не знает о подмене — он работает с тем же интерфейсом.
Ключевой момент: jest.mock применяется на уровне модуля,
а не внутри теста. Подмена происходит до выполнения импортов.
Вызовы jest.mock поднимаются вверх файла и выполняются
до всех import. Это означает:
jest.mock('../config', () => ({
API_URL: 'http://test.local',
}));
Если требуется доступ к данным во время выполнения, используется фабрика:
jest.mock('../utils/logger', () => {
return {
log: jest.fn(),
};
});
Фабрика выполняется один раз на файл, а не на каждый тест.
Иногда требуется сохранить реальное поведение части модуля.
jest.mock('../math', () => {
const actual = jest.requireActual('../math');
return {
...actual,
sum: jest.fn(),
};
});
Такой подход позволяет тестировать интеграцию, не разрушая контракт модуля целиком.
jest.spyOn используется для подмены конкретного метода
объекта или модуля без полной замены.
import * as api from '../api/userService';
jest.spyOn(api, 'getUser').mockResolvedValue({ id: 1 });
Преимущества:
Ограничение: метод должен существовать и быть доступен для записи.
Каждый mock хранит состояние: количество вызовов, аргументы, возвращаемые значения. Без очистки это состояние протекает между тестами.
Очищает историю вызовов, но сохраняет реализацию.
afterEach(() => {
jest.clearAllMocks();
});
Используется, когда mock-реализация одинакова для всех тестов.
Полностью сбрасывает mocks к изначальному состоянию.
afterEach(() => {
jest.resetAllMocks();
});
Важно: сбрасываются и реализации, заданные через
mockImplementation.
Возвращает оригинальные реализации для spies.
afterEach(() => {
jest.restoreAllMocks();
});
Актуально только для jest.spyOn. Для
jest.mock не влияет.
Jest кэширует загруженные модули. Если mock зависит от состояния или конфигурации, требуется сброс кэша.
jest.resetModules();
После этого следующий require или import
загрузит модуль заново.
Частый сценарий — тестирование разных конфигураций одного и того же модуля:
test('вариант A', () => {
jest.resetModules();
jest.doMock('../config', () => ({ mode: 'A' }));
const module = require('../module');
});
test('вариант B', () => {
jest.resetModules();
jest.doMock('../config', () => ({ mode: 'B' }));
const module = require('../module');
});
Для локальной изоляции без глобального сброса используется
jest.isolateModules.
jest.isolateModules(() => {
jest.doMock('../config', () => ({ debug: true }));
const module = require('../module');
});
Модули, загруженные внутри колбэка, не попадают в общий кэш.
jest.doMock не поднимается вверх файла и позволяет
управлять mock внутри теста.
jest.doMock('../featureFlag', () => ({
enabled: true,
}));
Обязательное условие — импорт через require, а не
import.
React Testing Library автоматически вызывает cleanup
после каждого теста (начиная с v9). Это удаляет компоненты из DOM и
предотвращает утечки.
import { render } from '@testing-library/react';
Дополнительный вызов cleanup требуется только при
отключённой автоматике или нестандартной конфигурации.
Важно различать:
Это независимые механизмы.
При использовании jest.useFakeTimers() состояние
таймеров также требует сброса.
afterEach(() => {
jest.useRealTimers();
});
Иначе фейковые таймеры могут повлиять на последующие тесты, особенно при асинхронном рендеринге.
Глобальный mock без очистки Приводит к зависимостям между тестами и нестабильным результатам.
Смешивание mockResolvedValue и
mockImplementation без reset Поведение функции
становится неочевидным.
Мокирование того, что тестируется Подмена внутренней логики компонента уничтожает смысл теста и снижает ценность проверки.
Использование jest.mock внутри
describe Визуально выглядит корректно, но
фактически применяется ко всему файлу.
Распространённый подход — вынос фабрик mocks в отдельные файлы:
// __mocks__/userService.js
export const getUser = jest.fn();
Jest автоматически подхватывает такие модули при вызове
jest.mock('../userService').
Преимущество — единое место для логики подмены и упрощённая очистка.
React Testing Library ориентирована на тестирование поведения, а не реализации. Модульные mocks должны:
Корректная очистка mocks поддерживает главный принцип: каждый тест — независимая, воспроизводимая единица.