Тестирование логики HOC

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

В этом разделе рассмотрим, как можно эффективно протестировать логику HOC с использованием Enzyme, библиотеки для тестирования React-компонентов.

Что такое HOC?

HOC — это функция, которая принимает компонент и возвращает новый компонент с дополнительной логикой. HOC не изменяет сам исходный компонент, а добавляет функциональность, например, обработку состояний, работу с контекстом, добавление стилей или API-запросов.

Пример простого HOC:

const withLoading = (Component) => {
  return function WithLoading(props) {
    if (props.isLoading) {
      return <div>Loading...</div>;
    }
    return <Component {...props} />;
  };
};

Здесь withLoading — это HOC, который принимает компонент и добавляет логику отображения сообщения “Loading…” до тех пор, пока isLoading не станет false.

Как тестировать HOC

Тестирование HOC можно разделить на несколько этапов. Основные задачи заключаются в проверке работы логики, передаваемой через HOC, а также в тестировании взаимодействия с компонентом, обернутым этим HOC. Важно помнить, что сам HOC, скорее всего, не будет напрямую рендериться в тестах. Вместо этого необходимо проверять, как он влияет на компонент, который является его «подопечным».

Тестирование с использованием Enzyme

Enzyme предоставляет несколько утилит для рендеринга и взаимодействия с компонентами, таких как shallow, mount и render. Для тестирования HOC чаще всего используется shallow, так как он позволяет рендерить компоненты на одном уровне, избегая рендеринга всех дочерних компонентов.

Пример теста для HOC withLoading:

import React from 'react';
import { shallow } from 'enzyme';
import withLoading from './withLoading';

describe('HOC withLoading', () => {
  const DummyComponent = () => <div>Data</div>;

  it('should display loading message when isLoading is true', () => {
    const WrappedComponent = withLoading(DummyComponent);
    const wrapper = shallow(<WrappedComponent isLoading={true} />);
    expect(wrapper.text()).toEqual('Loading...');
  });

  it('should render the wrapped component when isLoading is false', () => {
    const WrappedComponent = withLoading(DummyComponent);
    const wrapper = shallow(<WrappedComponent isLoading={false} />);
    expect(wrapper.find(DummyComponent).length).toBe(1);
  });
});

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

  1. Мы создаем обертку для компонента DummyComponent с помощью HOC withLoading.
  2. Проверяем, что при isLoading={true} компонент выводит текст “Loading…”.
  3. Проверяем, что при isLoading={false} обернутый компонент DummyComponent рендерится корректно.

Тестирование взаимодействия с пропсами

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

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

import React from 'react';
import { shallow } from 'enzyme';
import withLoading from './withLoading';

const DummyComponent = ({ message }) => <div>{message}</div>;

describe('HOC withLoading with props', () => {
  it('should pass correct props to the wrapped component', () => {
    const WrappedComponent = withLoading(DummyComponent);
    const wrapper = shallow(<WrappedComponent isLoading={false} message="Data loaded" />);
    expect(wrapper.find(DummyComponent).props().message).toEqual('Data loaded');
  });
});

Здесь мы проверяем, что пропс message передается в DummyComponent правильно, даже если компонент обернут в HOC.

Тестирование логики, добавленной HOC

В случае, если HOC содержит более сложную логику, например, управление состоянием или асинхронные запросы, тестирование будет включать проверку корректности этих процессов.

Пример HOC с асинхронной логикой:

const withData = (Component) => {
  return class WithData extends React.Component {
    state = { data: null, isLoading: true };

    componentDidMount() {
      setTimeout(() => {
        this.setState({ data: 'Fetched Data', isLoading: false });
      }, 1000);
    }

    render() {
      return <Component {...this.props} data={this.state.data} isLoading={this.state.isLoading} />;
    }
  };
};

Этот HOC делает асинхронный запрос (в данном случае через setTimeout) и передает данные в обернутый компонент. Важно протестировать правильную работу состояния isLoading и передачу данных.

Пример теста с асинхронной логикой:

import React from 'react';
import { shallow } from 'enzyme';
import withData from './withData';

const DummyComponent = ({ data, isLoading }) => (
  <div>
    {isLoading ? 'Loading...' : data}
  </div>
);

describe('HOC withData', () => {
  it('should initially show loading state', () => {
    const WrappedComponent = withData(DummyComponent);
    const wrapper = shallow(<WrappedComponent />);
    expect(wrapper.text()).toEqual('Loading...');
  });

  it('should show data after loading', () => {
    const WrappedComponent = withData(DummyComponent);
    const wrapper = shallow(<WrappedComponent />);
    
    // Симулируем ожидание завершения setTimeout
    jest.runAllTimers();
    
    wrapper.update();
    expect(wrapper.text()).toEqual('Fetched Data');
  });
});

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

  1. Мы проверяем, что при монтировании компонента показывается текст “Loading…”.
  2. После выполнения асинхронного запроса (симулированного с помощью jest.runAllTimers), проверяем, что отображаются полученные данные.

Использование mount для тестирования HOC

В некоторых случаях для тестирования сложных взаимодействий HOC необходимо использовать mount, который выполняет полный рендеринг компонента с его дочерними элементами. Это полезно, если вам нужно проверить взаимодействие с реальным DOM.

Пример использования mount:

import { mount } from 'enzyme';
import withLoading from './withLoading';

describe('HOC withLoading with mount', () => {
  const DummyComponent = () => <div>Data</div>;

  it('should display loading message with mount', () => {
    const WrappedComponent = withLoading(DummyComponent);
    const wrapper = mount(<WrappedComponent isLoading={true} />);
    expect(wrapper.text()).toEqual('Loading...');
  });
});

Заключение

Тестирование логики HOC требует учета нескольких аспектов: корректности работы обертки, передачи пропсов и взаимодействия с состоянием и асинхронными запросами. Enzyme предоставляет мощные инструменты для рендеринга и взаимодействия с компонентами, что позволяет тщательно проверять все уровни логики HOC.