Higher Order Components тестирование

Понимание Higher Order Components (HOC)

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

При тестировании компонентов с использованием React Testing Library, тестирование HOC может быть немного сложнее, чем обычных компонентов, поскольку HOC изменяет поведение переданного компонента. Необходимо уделить внимание тому, как HOC взаимодействует с состоянием и как он влияет на отображение компонентов.

Основные подходы к тестированию HOC

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

  1. Тестирование в контексте обертки Многие HOC используют контекст или другие механизмы для добавления функциональности компонентам. Это значит, что при тестировании нужно учитывать, как HOC оборачивает компонент. Например, если HOC использует контекст, тест должен убедиться, что компонент правильно получает данные из контекста.

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

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

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

Рассмотрим пример простого HOC, который оборачивает компонент и передает ему данные из состояния.

// HOC.js
import React, { useState } from 'react';

export function withCounter(Component) {
  return function WithCounter(props) {
    const [count, setCount] = useState(0);
    const increment = () => setCount(count + 1);

    return <Component {...props} count={count} increment={increment} />;
  };
}

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

Для тестирования можно использовать React Testing Library и Jest.

// Counter.js
import React from 'react';

export function Counter({ count, increment }) {
  return (
    <div>
      <p>{count}</p>
      <button onCl ick={increment}>Increment</button>
    </div>
  );
}
// App.js
import React from 'react';
import { withCounter } from './HOC';
import { Counter } from './Counter';

const EnhancedCounter = withCounter(Counter);

function App() {
  return <EnhancedCounter />;
}

export default App;

Теперь, чтобы протестировать withCounter, нужно удостовериться, что состояние счетчика работает корректно, и компонент обновляется после вызова функции increment.

// HOC.test.js
import { render, fireEvent, screen } from '@testing-library/react';
import { EnhancedCounter } from './App'; // импорт обёрнутого компонента

test('счётчик увеличивается при клике на кнопку', () => {
  render(<EnhancedCounter />);

  const button = screen.getByText(/Increment/i);
  const countText = screen.getByText(/0/);

  expect(countText).toBeInTheDocument();

  fireEvent.click(button);

  expect(screen.getByText(/1/)).toBeInTheDocument();
});

В данном тесте:

  • Мы рендерим компонент, обернутый в HOC withCounter.
  • Проверяем, что изначально отображается значение счётчика 0.
  • Симулируем клик по кнопке, которая увеличивает счётчик, и проверяем, что значение изменилось на 1.

Мокирование контекста в HOC

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

Пример:

// ThemeContext.js
import React, { createContext, useContext } from 'react';

const ThemeContext = createContext();

export function useTheme() {
  return useContext(ThemeContext);
}

export function ThemeProvider({ children }) {
  const theme = 'dark';
  return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;
}

HOC, который использует ThemeContext:

// withTheme.js
import React from 'react';
import { useTheme } from './ThemeContext';

export function withTheme(Component) {
  return function WithTheme(props) {
    const theme = useTheme();
    return <Component {...props} theme={theme} />;
  };
}

В тестах можно замокировать ThemeContext, чтобы проверить, как работает HOC:

import { render, screen } from '@testing-library/react';
import { withTheme } from './withTheme';
import { ThemeProvider } from './ThemeContext';

const ThemedComponent = withTheme(({ theme }) => <div>{theme}</div>);

test('HOC передает значение контекста', () => {
  render(
    <ThemeProvider>
      <ThemedComponent />
    </ThemeProvider>
  );

  expect(screen.getByText('dark')).toBeInTheDocument();
});

Здесь:

  • Компонент ThemedComponent обернут в HOC withTheme, который передает значение контекста в компонент.
  • Мы рендерим компонент внутри ThemeProvider, который предоставляет контекст.
  • Проверяем, что компонент получает правильное значение контекста.

Тестирование поведения и пропсов HOC

HOC может передавать дополнительные пропсы или функции, что также нужно тестировать. Например, если HOC добавляет обработчик событий или изменяет данные пропсов, тест должен проверять, как это влияет на поведение компонента.

Пример:

// HOC с добавлением обработчика события
export function withClickHandler(Component) {
  return function WithClickHandler(props) {
    const handleClick = () => alert('Clicked!');
    return <Component {...props} onCl ick={handleClick} />;
  };
}

Тестирование:

import { render, fireEvent } from '@testing-library/react';
import { withClickHandler } from './withClickHandler';

const Button = ({ onClick }) => <button onCl ick={onClick}>Click me</button>;

const EnhancedButton = withClickHandler(Button);

test('обработчик клика вызывается при нажатии на кнопку', () => {
  render(<EnhancedButton />);
  
  const button = screen.getByText(/Click me/i);
  const alertMock = jest.spyOn(window, 'alert').mockImplementation();

  fireEvent.click(button);

  expect(alertMock).toHaveBeenCalledWith('Clicked!');
});

В этом примере:

  • Мы создаем компонент Button, который принимает проп onClick.
  • HOC withClickHandler добавляет обработчик нажатия.
  • В тесте мы проверяем, что при клике на кнопку вызывается функция, переданная HOC.

Заключение

Тестирование HOC в React Testing Library требует внимания к контекстам, пропсам и поведению, которое добавляется обернутыми компонентами. Важно использовать такие инструменты, как мокирование и шпионов, для проверки правильности работы HOC и его воздействия на переданные компоненты.