.dive() для проникновения сквозь HOC

В тестировании React-компонентов с использованием Enzyme часто встречается ситуация, когда компоненты обёрнуты в Higher-Order Components (HOC). HOC добавляют функциональность к базовым компонентам, оборачивая их в дополнительные слои. При этом стандартный рендеринг через shallow возвращает внешний HOC, а не сам компонент, который нужно тестировать. Для таких случаев предназначен метод .dive().


Основная идея .dive()

Метод .dive() позволяет спуститься на один уровень внутрь обёрнутого компонента, effectively “прокинувшись” через HOC. Это особенно полезно для тестирования поведения самого компонента, а не его обёртки.

import { shallow } from 'enzyme';
import MyComponent from './MyComponent';
import withExtraProps from './withExtraProps';

const WrappedComponent = withExtraProps(MyComponent);

const wrapper = shallow();
console.log(wrapper.debug()); // Отображает HOC
const innerWrapper = wrapper.dive();
console.log(innerWrapper.debug()); // Отображает MyComponent

Ключевой момент: .dive() работает только с компонентами, обёрнутыми в HOC, которые возвращают классический или функциональный компонент, а не простые HTML-элементы.


Использование .dive() с несколькими уровнями HOC

Если компонент обёрнут в несколько HOC, можно вызывать .dive() последовательно:

const wrapper = shallow();
const firstDive = wrapper.dive(); // спускаемся через withRouter
const secondDive = firstDive.dive(); // спускаемся через withTheme

Для ускорения процесса часто используют цепочку вызовов:

const innerWrapper = shallow()
  .dive()
  .dive();

Отличие от .shallow()

  • shallow() рендерит только один уровень компонента.
  • .dive() позволяет войти внутрь этого уровня, не переходя к полному DOM-рендерингу через mount.

Таким образом, .dive() обеспечивает контроль над отдельными слоями компонента без необходимости рендерить всю дерево.


Особенности работы с функциональными компонентами и hooks

Функциональные компоненты, использующие React hooks, после .dive() ведут себя так же, как классовые компоненты. Основное ограничение — .dive() не работает с компонентами, которые возвращают null или фрагменты без оболочки, потому что Enzyme не видит “уровня для спуска”.


Примеры тестов с .dive()

Проверка рендеринга элементов внутреннего компонента:

const wrapper = shallow();
const inner = wrapper.dive();

expect(inner.find('button').length).toBe(1);
expect(inner.text()).toContain('Submit');

Проверка передачи props через HOC:

const wrapper = shallow();
const inner = wrapper.dive();

expect(inner.props().extraProp).toBe(true);

Вызов методов компонента:

const wrapper = shallow();
const inner = wrapper.dive();
inner.instance().handleClick();
expect(inner.state('clicked')).toBe(true);

Частые ошибки при использовании .dive()

  1. Попытка вызвать .dive() на DOM-элементе:
const wrapper = shallow(
); wrapper.dive(); // Ошибка: dive can only be called on a single non-DOM component
  1. Несоответствие версий Enzyme и адаптера React:
  • Для React 16+ необходимо использовать enzyme-adapter-react-16.
  • Несовпадение версии может привести к тому, что .dive() не будет работать с HOC.
  1. HOC, возвращающие Fragment:
  • Если HOC возвращает <>..., .dive() не имеет “уровня” для спуска.
  • Решение — оборачивать внутренний компонент в контейнерный элемент (div или span).

Рекомендации по использованию

  • Для единичного теста лучше использовать последовательные .dive(), чтобы точно попасть в нужный компонент.
  • Для сложных HOC рекомендуется использовать shallowUntilTarget (вспомогательная функция) для автоматизации спуска до целевого компонента.
  • Всегда проверять wrapper.debug() после .dive(), чтобы убедиться, что вы тестируете именно нужный компонент.