Мокирование (mocking) является важной частью тестирования, поскольку позволяет изолировать компоненты системы, имитируя их зависимости. В Jest мокирование применяется для того, чтобы заменить реальные реализации функций или объектов на их имитации. Это особенно полезно при тестировании компонентов, которые взаимодействуют с внешними сервисами, базами данных, API или другими частями системы, которые могут быть сложными в тестировании или несущественными для конкретного теста.
В Jest функции можно замокировать с помощью метода
jest.fn(). Этот метод создаёт мок-функцию, которая будет
вести себя как обычная функция, но при этом позволяет отслеживать её
вызовы, аргументы и результаты.
Пример:
const mockCallback = jest.fn();
mockCallback('arg1', 'arg2');
expect(mockCallback).toHaveBeenCalled();
expect(mockCallback).toHaveBeenCalledWith('arg1', 'arg2');
В данном примере создается мок-функция mockCallback,
которая вызывается с двумя аргументами. Далее, с помощью встроенных
матчеров, проверяется, был ли вызов функции и с какими аргументами.
В Jest можно замокировать целые модули с помощью функции
jest.mock(). Это позволяет заменять импорты в тестах на их
моки, что необходимо для изоляции тестируемой логики от внешних
зависимостей.
Пример мокирования модуля:
jest.mock('./someModule', () => ({
fetchData: jest.fn(() => Promise.resolve('mocked data')),
}));
const { fetchData } = require('./someModule');
test('fetchData возвращает замокированные данные', async () => {
const data = await fetchData();
expect(data).toBe('mocked data');
});
В этом примере модуль someModule мокируется с
замокированной версией функции fetchData, которая всегда
возвращает разрешённый промис с данными. Это позволяет тестировать
логику без необходимости делать реальные HTTP-запросы.
Для мокирования классов используется jest.fn() или
jest.mock(). Если необходимо замокировать класс, то вместо
его реальной реализации можно подставить имитацию, что позволяет
изолировать тестируемую логику от сложных зависимостей.
Пример мокирования класса:
jest.mock('./Database', () => {
return jest.fn().mockImplementation(() => {
return { save: jest.fn(() => 'data saved') };
});
});
const Database = require('./Database');
test('метод save работает корректно', () => {
const db = new Database();
const result = db.save();
expect(result).toBe('data saved');
expect(db.save).toHaveBeenCalled();
});
Здесь класс Database замокирован таким образом, чтобы
метод save всегда возвращал фиксированный результат, что
упрощает тестирование без подключения к реальной базе данных.
Часто необходимо мокировать асинхронные функции, которые используют
промисы или async/await. Jest предоставляет возможности для
мокирования таких функций, чтобы они всегда возвращали заранее
подготовленные значения.
Пример мокирования асинхронной функции:
jest.mock('./api', () => ({
fetchData: jest.fn().mockResolvedValue({ data: 'mocked data' }),
}));
const { fetchData } = require('./api');
test('fetchData возвращает замокированные данные', async () => {
const data = await fetchData();
expect(data).toEqual({ data: 'mocked data' });
});
В этом примере мокируется асинхронная функция fetchData,
которая возвращает промис с заранее подготовленным значением.
Jest предоставляет удобные инструменты для мокирования времени и
таймеров, что важно для тестирования асинхронного кода, который зависит
от временных событий (например, setTimeout,
setInterval, Date и т. д.).
Для мокирования таймеров можно использовать
jest.useFakeTimers() и методы
jest.advanceTimersByTime() для симуляции прохождения
времени.
Пример:
jest.useFakeTimers();
test('отсрочка вызова функции после timeout', () => {
const mockCallback = jest.fn();
setTimeout(mockCallback, 1000);
jest.advanceTimersByTime(1000);
expect(mockCallback).toHaveBeenCalled();
});
В данном примере таймер мокируется, и с помощью
jest.advanceTimersByTime() мы симулируем прохождение 1000
миллисекунд, что приводит к вызову замокированной функции.
После использования мока важно восстанавливать оригинальные функции
или модули, чтобы они не повлияли на другие тесты. Для этого Jest
предоставляет метод jest.restoreAllMocks(), который
восстанавливает все моки к их оригинальному состоянию.
Пример:
jest.mock('./someModule', () => ({
fetchData: jest.fn(() => 'mocked data'),
}));
test('тест с мокированием', () => {
const { fetchData } = require('./someModule');
expect(fetchData()).toBe('mocked data');
});
afterAll(() => {
jest.restoreAllMocks();
});
После выполнения тестов моки будут восстановлены, чтобы не повлиять на последующие тесты, что является хорошей практикой для поддержания чистоты тестового окружения.
Иногда необходимо создать мок, который будет вести себя как реальная
функция, но с возможностью управления её поведением. Для этого в Jest
существует метод mockImplementation, который позволяет
задать логику для мокированного объекта или функции.
Пример:
const mockFn = jest.fn();
mockFn.mockImplementation((arg) => `Hello, ${arg}`);
test('mockFn работает с переданным аргументом', () => {
expect(mockFn('World')).toBe('Hello, World');
});
Здесь мок-функция mockFn ведёт себя как обычная функция,
но при этом её поведение можно гибко настроить с помощью
mockImplementation.
Мокирование зависимостей в Jest позволяет значительно упростить тестирование компонентов, изолируя их от внешних факторов и упрощая взаимодействие с зависимостями. Оно предоставляет мощные инструменты для замены реальных функций, классов и модулей на их имитации, что делает тесты более быстрыми, независимыми и предсказуемыми. Мокирование используется для создания контролируемых условий, в которых можно точно проверять работу тестируемого кода, не полагаясь на внешние или сложные ресурсы.