jest.requireActual для частичного реального компонента

В тестировании React-приложений часто возникает ситуация, когда требуется заменить часть поведения модуля на мок, но при этом сохранить остальные функции или компоненты в их реальном виде. Для таких случаев Jest предоставляет функцию jest.requireActual, которая позволяет загружать реальную реализацию модуля, даже если она будет частично мокирована.


Основные принципы работы

jest.requireActual(moduleName) возвращает оригинальный модуль, как если бы никакого мока не было. Это особенно полезно при комбинировании моков и реального поведения:

import * as utils from './utils';

jest.mock('./utils', () => {
  const actualUtils = jest.requireActual('./utils');
  return {
    ...actualUtils,
    fetchData: jest.fn(() => Promise.resolve({ data: [] })),
  };
});

В данном примере:

  • fetchData заменяется на мок с предопределённым результатом.
  • Все остальные функции из utils остаются неизменными и работают как в реальном коде.

Частичный мок для компонентов React

В React иногда нужно протестировать компонент, который импортирует другой компонент, но при этом часть внутреннего поведения зависимого компонента должна оставаться реальной.

Пример с компонентами:

import React from 'react';
import { render, screen } from '@testing-library/react';
import ParentComponent from './ParentComponent';
import * as ChildModule from './ChildComponent';

jest.mock('./ChildComponent', () => {
  const actualChild = jest.requireActual('./ChildComponent');
  return {
    ...actualChild,
    ChildComponent: (props) => (
      <div data-testid="mocked-child">
        Мокнутый дочерний компонент
      </div>
    ),
  };
});

test('ParentComponent рендерит частично замокнутый ChildComponent', () => {
  render(<ParentComponent />);
  expect(screen.getByTestId('mocked-child')).toBeInTheDocument();
});

Здесь:

  • Реальный модуль ChildComponent импортируется через jest.requireActual.
  • Только конкретный экспорт (ChildComponent) заменяется на кастомный мок.
  • Остальные экспорты остаются нетронутыми.

Комбинация с React Testing Library

Использование jest.requireActual в связке с RTL позволяет создавать гибкие тесты, которые:

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

Пример интеграционного теста:

import React from 'react';
import { render, screen } from '@testing-library/react';
import Dashboard from './Dashboard';
import * as api from './api';

jest.mock('./api', () => {
  const actualApi = jest.requireActual('./api');
  return {
    ...actualApi,
    getUserStats: jest.fn(() => Promise.resolve({ visits: 42 })),
  };
});

test('Dashboard отображает реальные компоненты и замоканные данные', async () => {
  render(<Dashboard />);
  expect(await screen.findByText(/42 визитов/)).toBeInTheDocument();
});

Преимущества такого подхода:

  • Стабильность тестов: асинхронные вызовы возвращают предсказуемый результат.
  • Гибкость: реальные компоненты и утилиты продолжают работать.
  • Изоляция логики: можно мокировать только проблемные части, не влияя на остальную систему.

Особенности и подводные камни

  1. Циклические зависимости Использование jest.requireActual при циклических импортов может привести к неожиданным результатам, так как Jest загружает модуль в момент вызова функции. В таких случаях важно проверять порядок инициализации моков.

  2. Частичный мок по умолчанию Если модуль экспортирует объект по умолчанию (default export), доступ к нему через jest.requireActual нужно получать следующим образом:

    const actualModule = jest.requireActual('./module').default;
  3. Смешение моков и реальных функций При объединении мока и реальных функций через spread-оператор (...) необходимо следить, чтобы имена экспортов совпадали. В противном случае тест может использовать неверную реализацию.


Практические советы

  • Использовать jest.requireActual только для модулей, где необходимо частичное сохранение реального поведения.
  • Мокать только конкретные функции или компоненты, не заменяя весь модуль.
  • При работе с асинхронными функциями рекомендуется возвращать предопределённые промисы, чтобы тесты оставались детерминированными.
  • В комбинации с React Testing Library это позволяет создавать интеграционные тесты компонентов, сохраняя контроль над данными и взаимодействиями.

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