Мокирование node_modules компонентов

Мокирование внешних компонентов и модулей из node_modules является критически важным при написании unit-тестов на React. Оно позволяет изолировать тестируемый компонент, ускоряет выполнение тестов и снижает вероятность возникновения непредсказуемых побочных эффектов. Рассмотрим этот процесс подробно.


Основы мокирования

Мок (mock) — это объект или функция, которые имитируют поведение настоящего модуля. В контексте React Testing Library это чаще всего:

  • React-компоненты из внешних библиотек (react-router-dom, antd, material-ui);
  • Функции из утилитарных модулей (lodash, axios и др.).

Мокирование позволяет заменить сложную реализацию простыми заглушками, которые возвращают предсказуемый результат.


Мокирование с помощью jest.mock

Самый распространённый способ — использование функции jest.mock. Синтаксис:

jest.mock('имя_модуля', () => ({
  // Экспортируем необходимые функции или компоненты
}));

Пример: мокирование компонента Button из @mui/material:

import { render, screen } from '@testing-library/react';
import MyComponent from './MyComponent';
import React from 'react';

jest.mock('@mui/material', () => {
  const originalModule = jest.requireActual('@mui/material');
  return {
    ...originalModule,
    Button: (props) => <button {...props} />,
  };
});

test('рендерит кнопку', () => {
  render(<MyComponent />);
  expect(screen.getByRole('button')).toBeInTheDocument();
});

Ключевые моменты:

  • Используется jest.requireActual для сохранения неизменных экспоротов модуля.
  • Мок-компонент должен корректно передавать props для совместимости с тестируемым компонентом.

Мокирование функций из библиотек

Помимо компонентов, часто требуется мокировать функции:

import axios from 'axios';
import { fetchData } from './api';

jest.mock('axios');

test('fetchData возвращает данные', async () => {
  axios.get.mockResolvedValue({ data: { name: 'React' } });

  const result = await fetchData();
  expect(result).toEqual({ name: 'React' });
});

Особенности:

  • Используется mockResolvedValue для промисов, mockReturnValue для синхронных функций.
  • Мокирование должно происходить до импорта тестируемого модуля, иначе оригинальная реализация будет использоваться.

Автоматическое мокирование с __mocks__

Для крупных проектов удобно создавать папку __mocks__ рядом с node_modules или внутри проекта. Jest автоматически подхватывает моки из неё.

Пример структуры:

project/
 ├─ __mocks__/
 │   └─ axios.js
 └─ src/
     └─ api.js

Содержимое __mocks__/axios.js:

export default {
  get: jest.fn(() => Promise.resolve({ data: {} })),
};

Такой подход позволяет централизованно управлять всеми моками, упрощает поддержку тестов и делает их более предсказуемыми.


Мокирование компонента с внутренними зависимостями

Иногда компонент импортирует другие компоненты из node_modules, которые, в свою очередь, зависят от глобальных контекстов или стилей. В таких случаях:

jest.mock('react-router-dom', () => ({
  ...jest.requireActual('react-router-dom'),
  Link: ({ children }) => <a>{children}</a>,
}));

Особенности:

  • Сохраняется функциональность остальных экспортов (useNavigate, useParams), чтобы тесты не ломались.
  • Простой заменяемый компонент (например, <a> вместо <Link>) предотвращает ошибки рендеринга.

Комбинированное мокирование

В реальных проектах часто требуется одновременно мокировать несколько модулей:

jest.mock('axios');
jest.mock('@mui/material', () => ({
  Button: (props) => <button {...props} />,
  TextField: (props) => <input {...props} />,
}));

Советы:

  • Моки должны быть максимально простыми и предсказуемыми.
  • Не стоит мокировать всё подряд — это скрывает реальные ошибки.
  • Для сложных компонентов лучше использовать shallow-рендеринг через @testing-library/react и мокировать только критические зависимости.

Поддержка TypeScript при мокировании

При работе с TypeScript важно корректно типизировать моки:

import axios, { AxiosResponse } from 'axios';

jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

mockedAxios.get.mockResolvedValue({ data: { id: 1 } } as AxiosResponse);

Это предотвращает ошибки компиляции и обеспечивает автодополнение в IDE.


Рекомендации по эффективному мокированию

  • Мокировать только внешние зависимости, оставляя собственные компоненты тестируемыми.
  • Использовать jest.resetAllMocks() или jest.clearAllMocks() между тестами для предотвращения утечек состояния.
  • Сохранять читаемость тестов — мок не должен усложнять понимание того, что тестируется.
  • Для UI-библиотек предпочтительно заменять сложные компоненты простыми HTML-элементами.

Мокирование node_modules компонентов является основой стабильного и быстрого тестирования в React. Оно обеспечивает контроль над внешними зависимостями, уменьшает количество фейлов в тестах и позволяет сосредоточиться на поведении собственного кода.