Тестирование взаимодействия между модулями — одна из важных составляющих процесса обеспечения качества программного обеспечения. Оно позволяет убедиться, что различные части системы корректно работают вместе. В Jest можно эффективно тестировать такие взаимодействия, используя различные подходы, такие как мокирование, шпионские функции и интеграционные тесты.
Мокирование — это процесс создания подделок (моков) для зависимостей модуля, чтобы тестировать его изолированно от внешних компонентов. Это особенно полезно, если внешний компонент сложно или невозможно интегрировать в тесты, либо если его поведение предсказуемо и не имеет смысла тестировать в контексте текущего теста.
В Jest для мокирования используется функция jest.mock().
Она позволяет замещать реальную зависимость на поддельную, что упрощает
тестирование взаимодействий.
// Мокирование функции
jest.mock('./api', () => ({
fetchData: jest.fn().mockResolvedValue('some data'),
}));
import { fetchData } from './api';
test('fetchData должен вернуть правильные данные', async () => {
const data = await fetchData();
expect(data).toBe('some data');
});
В данном примере, реальная зависимость fetchData из
модуля api заменена мок-функцией, которая всегда возвращает
результат ‘some data’. Это позволяет тестировать логику, зависящую от
этой функции, не выполняя настоящие сетевые запросы.
Иногда необходимо проверять, что определённая функция была вызвана с нужными аргументами или с определённой частотой. Для этого в Jest используются шпионы, которые позволяют следить за функциями, не изменяя их поведения.
test('проверка вызова функции', () => {
const callback = jest.fn();
someFunction(callback);
expect(callback).toHaveBeenCalled();
expect(callback).toHaveBeenCalledWith('expected argument');
});
В этом примере jest.fn() создаёт шпионскую функцию,
которая отслеживает количество её вызовов и переданные аргументы. Это
удобно для проверки того, как модули взаимодействуют, например, при
вызове колбэков или обработчиков событий.
Интеграционные тесты фокусируются на проверке взаимодействия нескольких компонентов системы. В отличие от юнит-тестов, которые изолируют компоненты друг от друга, интеграционные тесты предполагают работу с реальными зависимостями, что позволяет проверить их совместимость и правильность взаимодействия.
Для интеграционного теста может потребоваться использование реальных данных или зависимостей, таких как базы данных или внешние API. Jest предоставляет удобные средства для работы с асинхронными операциями, что делает его отличным инструментом для интеграционных тестов.
Пример интеграционного теста с использованием реальной базы данных:
import { saveUser, getUser } from './userService';
test('интеграционный тест: сохранение и извлечение пользователя', async () => {
const user = { name: 'John Doe', email: 'john@example.com' };
await saveUser(user);
const retrievedUser = await getUser(user.email);
expect(retrievedUser).toEqual(user);
});
Здесь мы тестируем взаимодействие между сервисами сохранения и получения пользователя, проверяя, что данные корректно сохраняются и извлекаются.
Когда взаимодействие между модулями сложное и включает несколько уровней зависимостей, можно использовать более сложное мокирование. Jest позволяет мокировать только часть модуля, оставляя другие его части без изменений.
jest.mock('./service', () => ({
fetchData: jest.fn(),
}));
test('мокирование части модуля', () => {
const service = require('./service');
service.fetchData.mockResolvedValueOnce('mocked data');
// Вызов тестируемой функции, которая зависит от fetchData
expect(service.fetchData()).resolves.toBe('mocked data');
});
В этом примере только часть модуля service — функция
fetchData — мокируется, в то время как остальная часть
остаётся неизменной. Это может быть полезно для тестирования отдельных
частей модуля, при этом не затрагивая другие его компоненты.
Иногда бывает полезно использовать мокирование только для некоторых зависимостей, оставив другие модули работать с реальными данными. Такой подход позволяет проверить, как модуль взаимодействует с внешними сервисами или базами данных, одновременно изолируя его от сложных или трудно тестируемых зависимостей.
jest.mock('./externalAPI', () => ({
fetchData: jest.fn().mockResolvedValue('mocked data'),
}));
test('тест с реальной зависимостью и мокированием части модуля', async () => {
const result = await someFunctionThatUsesExternalAPI();
expect(result).toBe('mocked data');
});
Здесь внешнее API замещается мок-версией, в то время как взаимодействие с реальной частью логики остаётся неизменным.
Модульные и интеграционные тесты часто включают асинхронные операции,
такие как запросы к серверу, операции с базой данных или взаимодействие
с внешними сервисами. Jest предоставляет несколько способов работы с
асинхронным кодом, включая async/await, промисы и методы,
такие как done.
test('тестирование асинхронной функции', async () => {
const result = await someAsyncFunction();
expect(result).toBe('expected result');
});
В случае работы с асинхронными операциями важно правильно обрабатывать их завершение, чтобы избежать ложных срабатываний тестов.
Для тестирования взаимодействия модулей также необходимо учитывать побочные эффекты. Это могут быть изменения глобального состояния, вызовы функций или изменение значений переменных, которые могут влиять на другие части системы.
Jest позволяет отслеживать изменения с помощью шпионов и моков, а
также с помощью специальных матчеров, таких как
toHaveBeenCalledTimes() или
toHaveBeenLastCalledWith().
test('проверка изменения состояния при взаимодействии модулей', () => {
const stateChanger = jest.fn();
someFunctionThatChangesState(stateChanger);
expect(stateChanger).toHaveBeenCalledTimes(1);
});
Это позволяет убедиться, что при взаимодействии между модулями корректно изменяется состояние и что побочные эффекты происходят в нужный момент.
Не менее важным аспектом взаимодействия между модулями является корректная обработка ошибок и исключений. Тестирование должно включать проверку того, что модули правильно обрабатывают ошибки, возникающие в процессе взаимодействия.
Jest позволяет тестировать ошибки с использованием
rejects для асинхронных функций.
test('проверка обработки ошибки', async () => {
const errorFunction = jest.fn().mockRejectedValue(new Error('Something went wrong'));
await expect(errorFunction()).rejects.toThrow('Something went wrong');
});
Этот тест проверяет, что функция корректно выбрасывает ошибку, и позволяет убедиться в том, что ошибки обрабатываются правильно в контексте взаимодействия модулей.
Тестирование взаимодействий между модулями требует комплексного подхода. Важно не только проверять функциональность отдельных компонентов, но и то, как они работают в связке друг с другом. Мокирование, шпионы, интеграционные тесты, асинхронные операции и проверка побочных эффектов позволяют гарантировать, что модули системы взаимодействуют правильно и эффективно.