Мокирование контекста значений

React Context API предоставляет способ передачи данных через дерево компонентов без необходимости явно передавать их через пропсы. Этот механизм широко используется для управления состоянием приложения, темами, языковыми настройками и многим другим. Тестирование компонентов, использующих контекст, требует специального подхода, поскольку необходимо «мокировать» значения контекста для обеспечения корректных и изолированных тестов. В React Testing Library мокирование контекста — важная часть подготовки среды для тестирования.

Зачем моки контекста?

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

Создание контекста и провайдера

Предположим, что в приложении используется контекст для управления текущей темой оформления:

import React, { createContext, useState, useContext } from 'react';

const ThemeContext = createContext();

export const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export const useTheme = () => useContext(ThemeContext);

В этом примере ThemeProvider предоставляет значения для контекста, которые включают состояние темы и функцию для её переключения. Теперь для тестирования компонентов, использующих этот контекст, необходимо будет мокировать контекст в тестах.

Мокирование контекста для тестов

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

Пример компонента, который использует контекст:

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

const ThemedComponent = () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <div>
      <p>Текущая тема: {theme}</p>
      <button onCl ick={toggleTheme}>Переключить тему</button>
    </div>
  );
};

export default ThemedComponent;

Чтобы протестировать этот компонент, необходимо обернуть его в тестах в мокированный провайдер, который будет передавать нужные значения контекста:

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

const renderWithContext = (ui, { theme = 'light' } = {}) => {
  return render(
    <ThemeProvider>
      {ui}
    </ThemeProvider>
  );
};

test('проверка изменения темы', () => {
  renderWithContext(<ThemedComponent />);

  const button = screen.getByText('Переключить тему');
  const paragraph = screen.getByText(/Текущая тема/);

  // Проверяем начальное значение
  expect(paragraph).toHaveTextContent('Текущая тема: light');

  fireEvent.click(button);

  // Проверяем, что тема переключилась
  expect(paragraph).toHaveTextContent('Текущая тема: dark');
});

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

Мокирование контекста с кастомными значениями

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

const renderWithCustomContext = (ui, contextValue) => {
  return render(
    <ThemeContext.Provider value={contextValue}>
      {ui}
    </ThemeContext.Provider>
  );
};

test('проверка с кастомными значениями контекста', () => {
  renderWithCustomContext(<ThemedComponent />, { theme: 'dark', toggleTheme: jest.fn() });

  const paragraph = screen.getByText(/Текущая тема/);

  // Проверяем, что начальная тема соответствует переданному значению
  expect(paragraph).toHaveTextContent('Текущая тема: dark');
});

Здесь контекст передается напрямую через ThemeContext.Provider, и тестирует компонент в условиях, когда контекст имеет конкретное значение, отличное от дефолтного.

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

Если в контексте используются функции, такие как toggleTheme, для тестирования можно подменить их на заглушки или моки. В примере выше toggleTheme можно заменить на мок-функцию с помощью jest.fn().

const mockToggleTheme = jest.fn();

test('проверка вызова toggleTheme', () => {
  renderWithCustomContext(<ThemedComponent />, { theme: 'light', toggleTheme: mockToggleTheme });

  const button = screen.getByText('Переключить тему');
  fireEvent.click(button);

  expect(mockToggleTheme).toHaveBeenCalled();
});

Этот подход позволяет тестировать, что при определённом взаимодействии функция контекста была вызвана, и при необходимости проверять её поведение.

Мокирование контекста с помощью useContext

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

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

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

test('проверка состояния контекста через useContext', () => {
  const TestComponent = () => {
    const { theme } = useContext(ThemeContext);
    return <p>Тема: {theme}</p>;
  };

  render(
    <ThemeContext.Provider value={{ theme: 'dark' }}>
      <TestComponent />
    </ThemeContext.Provider>
  );

  const paragraph = screen.getByText('Тема: dark');
  expect(paragraph).toBeInTheDocument();
});

Здесь компонент напрямую использует useContext, и его состояние проверяется через рендер тестового компонента, который получает значение из контекста.

Заключение

Мокирование контекста в React Testing Library позволяет эффективно изолировать компоненты, проверяя их поведение в различных состояниях контекста. Использование кастомных провайдеров, мокирование функций и состояния контекста помогает создать точные и независимые тесты, которые не зависят от реального состояния приложения. Эти подходы дают возможность контролировать данные, передаваемые через контекст, и тестировать поведение компонентов в разных сценариях.