Context API и тестирование

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

Основы Context API

Контекст API состоит из нескольких частей:

  • React.createContext() — создаёт объект контекста, который включает два элемента: Provider и Consumer.
  • Provider — компонент, который передаёт значение контекста всем своим дочерним компонентам.
  • Consumer — компонент, который потребляет значение контекста, предоставляемое ближайшим Provider.

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

const MyContext = React.createContext();

function MyProvider({ children }) {
  const value = 'Hello from context';
  return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
}

function MyComponent() {
  return (
    <MyContext.Consumer>
      {(value) => <div>{value}</div>}
    </MyContext.Consumer>
  );
}

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

Тестирование компонентов с использованием Context API

Тестирование компонентов, использующих контекст, может представлять определённые трудности. Основные проблемы заключаются в том, что контекст может быть глубоко вложен, и необходимо обеспечить, чтобы тестируемые компоненты получали правильное значение контекста.

Для тестирования таких компонентов важно, чтобы тесты могли использовать реальное значение контекста. Для этого существуют несколько подходов, включая использование render-функции из популярных библиотек, таких как @testing-library/react, или прямое использование mount из библиотеки Enzyme.

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

Enzyme позволяет тестировать компоненты, использующие Context API, путём обёртывания тестируемого компонента в соответствующий Provider и передавая необходимое значение контекста.

Использование Enzyme для тестирования с Context API

  1. Создание контекста и компонентов

Создадим простое приложение, использующее контекст для передачи текущего языка:

const LanguageContext = React.createContext();

function LanguageProvider({ children }) {
  const [language, setLanguage] = React.useState('en');
  return (
    <LanguageContext.Provider value={{ language, setLanguage }}>
      {children}
    </LanguageContext.Provider>
  );
}

function LanguageDisplay() {
  return (
    <LanguageContext.Consumer>
      {({ language }) => <div>{language}</div>}
    </LanguageContext.Consumer>
  );
}
  1. Написание теста с Enzyme

Вместо использования @testing-library/react мы можем использовать Enzyme для монтирования компонента с контекстом. Для этого важно, чтобы LanguageProvider был обёрнут вокруг компонента, который использует LanguageContext.Consumer.

Пример теста:

import { shallow, mount } from 'enzyme';
import { LanguageProvider, LanguageDisplay, LanguageContext } from './MyComponent';

describe('LanguageDisplay Component', () => {
  it('should display the language from context', () => {
    const wrapper = mount(
      <LanguageProvider>
        <LanguageDisplay />
      </LanguageProvider>
    );
    expect(wrapper.find('div').text()).toBe('en');
  });

  it('should update the language on context change', () => {
    const wrapper = mount(
      <LanguageProvider>
        <LanguageDisplay />
      </LanguageProvider>
    );
    const newLanguage = 'es';
    wrapper.find(LanguageContext.Provider).setProps({ value: { language: newLanguage, setLanguage: jest.fn() } });
    expect(wrapper.find('div').text()).toBe(newLanguage);
  });
});

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

  1. Использование setProps для обновления контекста

Поскольку контекст — это механизм, который передаёт данные через дерево компонентов, изменения в контексте должны обновлять компоненты, которые его потребляют. Чтобы протестировать этот процесс, в Enzyme можно использовать метод setProps, чтобы изменять значение контекста в тестах.

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

it('should update the language when context value changes', () => {
  const wrapper = mount(
    <LanguageProvider>
      <LanguageDisplay />
    </LanguageProvider>
  );

  // Проверка первоначального значения
  expect(wrapper.find('div').text()).toBe('en');

  // Обновление контекста на новый язык
  wrapper.find(LanguageContext.Provider).setProps({
    value: { language: 'fr', setLanguage: jest.fn() }
  });

  // Проверка обновленного значения
  expect(wrapper.find('div').text()).toBe('fr');
});

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

  1. Мокирование функции контекста

Если контекст передаёт функции (например, setState или другие обработчики событий), то можно замокировать эти функции для проверки их вызова.

Пример:

function LanguageSwitcher() {
  const { language, setLanguage } = React.useContext(LanguageContext);
  return (
    <div>
      <span>{language}</span>
      <button onCl ick={() => setLanguage('fr')}>Switch to French</button>
    </div>
  );
}

it('should call setLanguage when the button is clicked', () => {
  const setLanguageMock = jest.fn();
  const wrapper = mount(
    <LanguageProvider>
      <LanguageContext.Provider value={{ language: 'en', setLanguage: setLanguageMock }}>
        <LanguageSwitcher />
      </LanguageContext.Provider>
    </LanguageProvider>
  );

  wrapper.find('button').simulate('click');
  expect(setLanguageMock).toHaveBeenCalledWith('fr');
});

В этом тесте мы замещаем функцию setLanguage на мок-функцию jest.fn(), что позволяет нам отслеживать её вызовы. После симуляции клика на кнопку проверяется, что функция была вызвана с правильным аргументом.

Заключение

Тестирование компонентов, использующих Context API, требует аккуратного подхода, так как необходимо учитывать, как контекст передаёт данные через дерево компонентов. Использование Enzyme для монтирования и тестирования таких компонентов позволяет создавать гибкие тесты, которые проверяют корректность работы контекста в приложении.

Правильное тестирование контекста с Enzyme может включать как проверку начальных значений, так и динамическое изменение значений через setProps. Замокированные функции и манипуляции с контекстом помогают детально тестировать взаимодействие между компонентами и их зависимости.