Partial mocks представляют собой технику тестирования, когда замещается лишь часть модуля или объекта, сохраняя остальную функциональность нетронутой. В React-приложениях это особенно полезно для управления зависимостями компонентов и внешними модулями, не нарушая основной логики.
В отличие от полного мокирования, при котором весь модуль подменяется фиктивной реализацией, partial mock позволяет:
Это особенно актуально при тестировании компонентов, которые используют сторонние утилиты или API-клиенты, где требуется изолировать лишь определённые вызовы.
Jest предоставляет встроенные возможности для частичного мокирования
модулей через комбинацию jest.mock() и
jest.requireActual().
Пример:
// utils.js
export const fetchData = async () => {
const response = await fetch('/data');
return response.json();
};
export const helper = (value) => value * 2;
Если нужно мокировать только fetchData, сохранив
helper:
import * as utils from './utils';
jest.mock('./utils', () => ({
...jest.requireActual('./utils'), // сохраняем реальные функции
fetchData: jest.fn(() => Promise.resolve({ data: 123 })), // мокируем только fetchData
}));
test('partial mock fetchData', async () => {
const result = await utils.fetchData();
expect(result).toEqual({ data: 123 });
expect(utils.helper(2)).toBe(4); // реальная функция работает
});
Ключевой момент — использование jest.requireActual().
Это гарантирует, что все функции, не указанные в моках, сохранят
оригинальное поведение.
В React Testing Library часто возникает необходимость замокировать части компонентов, например, внутренние хелперы или API-запросы, не заменяя весь компонент целиком.
// MyComponent.js
import React, { useEffect, useState } from 'react';
import { fetchData } from './api';
export function MyComponent() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data ? data.value : 'Loading...'}</div>;
}
Partial mock для тестирования:
import { render, screen, waitFor } from '@testing-library/react';
import * as api from './api';
import { MyComponent } from './MyComponent';
jest.mock('./api', () => ({
...jest.requireActual('./api'),
fetchData: jest.fn(() => Promise.resolve({ value: 'Mocked!' })),
}));
test('renders mocked data', async () => {
render(<MyComponent />);
await waitFor(() => expect(screen.getByText('Mocked!')).toBeInTheDocument());
});
В этом примере fetchData полностью контролируется
тестом, а остальные функции в api остаются
оригинальными.
Иногда требуется замокировать не модуль, а методы объекта,
передаваемого компоненту через пропсы или контекст. Используется
комбинация jest.spyOn() и
mockImplementation.
const apiClient = {
getUser: () => fetch('/user').then(res => res.json()),
getPosts: () => fetch('/posts').then(res => res.json()),
};
test('partial spy on getUser', async () => {
const spy = jest.spyOn(apiClient, 'getUser').mockResolvedValue({ id: 1, name: 'Alice' });
const user = await apiClient.getUser();
const posts = await apiClient.getPosts();
expect(user.name).toBe('Alice'); // мок
expect(posts).toBeDefined(); // реальная функция работает
spy.mockRestore(); // восстановление оригинального метода
});
Использование jest.spyOn() позволяет:
Partial mocks часто применяются в сочетании с асинхронными вызовами, например, при мокировании API-запросов. Рекомендуется:
mockResolvedValue или
mockRejectedValue вместо mockImplementation,
чтобы сразу возвращать промис.waitFor из React Testing
Library, чтобы дождаться обновления компонента.Пример:
jest.spyOn(api, 'fetchData').mockResolvedValue({ value: 'Async Mock' });
render(<MyComponent />);
await waitFor(() => expect(screen.getByText('Async Mock')).toBeInTheDocument());
mockRestore() для spyOn или
сбрасывать моки через jest.resetAllMocks().Partial mocks — мощный инструмент для тонкой настройки тестов. Они позволяют контролировать критические зависимости компонентов, минимально вмешиваясь в остальную функциональность, обеспечивая при этом стабильные и читаемые тесты. Это идеальный компромисс между полным мокированием и интеграционным тестированием.