Тестирование useContext хука

React Context предоставляет способ передачи данных через дерево компонентов без необходимости явно прокидывать пропсы на каждом уровне. Хук useContext позволяет функциональным компонентам получать доступ к этим данным напрямую. Тестирование компонентов, использующих useContext, требует понимания структуры контекста и способов его мокирования или обёртки в тестах.


Основы работы с useContext

Компоненты, использующие useContext, зависят от провайдера контекста, который должен быть доступен в дереве компонентов. Пример:

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

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>Click me</button>;
}

Здесь ThemedButton использует ThemeContext для получения темы. Без провайдера тестирование такого компонента приведет к использованию значения по умолчанию ('light').


Обёртка компонента в тестах

Для корректного тестирования компонентов с контекстом необходимо оборачивать их в соответствующий провайдер. В React Testing Library это делается через компонент-обёртку:

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

test('ThemedButton отображается с правильной темой', () => {
  render(
    <ThemeContext.Provider value="dark">
      <ThemedButton />
    </ThemeContext.Provider>
  );

  const button = screen.getByRole('button', { name: /click me/i });
  expect(button).toHaveClass('dark');
});

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


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

Если в проекте несколько компонентов используют один и тот же контекст, удобно создавать вспомогательную функцию для рендера:

function renderWithTheme(ui, { theme = 'light' } = {}) {
  return render(
    <ThemeContext.Provider value={theme}>
      {ui}
    </ThemeContext.Provider>
  );
}

test('ThemedButton с темой dark', () => {
  renderWithTheme(<ThemedButton />, { theme: 'dark' });
  expect(screen.getByRole('button')).toHaveClass('dark');
});

Такой подход уменьшает дублирование кода и делает тесты более читаемыми.


Тестирование нескольких контекстов

Компоненты могут использовать несколько контекстов одновременно:

const AuthContext = createContext({ user: null });

function Dashboard() {
  const { user } = useContext(AuthContext);
  const theme = useContext(ThemeContext);
  
  return <div className={`${theme} dashboard`}>{user ? user.name : 'Guest'}</div>;
}

Тестирование требует оборачивания компонента во все необходимые провайдеры:

test('Dashboard отображает имя пользователя с темой dark', () => {
  render(
    <ThemeContext.Provider value="dark">
      <AuthContext.Provider value={{ user: { name: 'Alice' } }}>
        <Dashboard />
      </AuthContext.Provider>
    </ThemeContext.Provider>
  );

  const dashboard = screen.getByText('Alice');
  expect(dashboard).toHaveClass('dark');
});

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


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

В сложных случаях можно замокировать useContext напрямую. Это полезно, если тестируемый компонент импортирует контекст из внешнего файла:

import * as React from 'react';
import { render, screen } from '@testing-library/react';
import { ThemedButton } from './ThemedButton';
import * as ThemeModule from './ThemeContext';

jest.spyOn(React, 'useContext').mockImplementation((context) => {
  if (context === ThemeModule.ThemeContext) return 'mocked-theme';
  return React.useContext(context);
});

test('ThemedButton с замокированным контекстом', () => {
  render(<ThemedButton />);
  expect(screen.getByRole('button')).toHaveClass('mocked-theme');
});

Важно: такой подход следует использовать экономно. Лучше предпочитать тестирование через провайдеры, так как мокирование useContext делает тест менее реалистичным.


Проверка реакции на изменение контекста

Контекст может изменяться динамически. В тестах важно проверять, что компонент корректно реагирует на изменения:

import { useState } from 'react';

function ThemeSwitcher() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>
      <ThemedButton />
      <button onCl ick={() => setTheme('dark')}>Switch</button>
    </ThemeContext.Provider>
  );
}

test('ThemedButton меняет класс при смене темы', () => {
  render(<ThemeSwitcher />);
  
  const button = screen.getByRole('button', { name: /click me/i });
  expect(button).toHaveClass('light');
  
  screen.getByRole('button', { name: /switch/i }).click();
  expect(button).toHaveClass('dark');
});

Тест демонстрирует, что изменения в состоянии контекста автоматически отражаются на компоненте, использующем useContext.


Проверка вызовов функций из контекста

Контексты часто содержат функции (например, обработчики событий или методы API). В этом случае проверка включает мок-функции:

const AuthContext = createContext({ login: () => {} });

function LoginButton() {
  const { login } = useContext(AuthContext);
  return <button onCl ick={login}>Login</button>;
}

test('LoginButton вызывает login из контекста', () => {
  const mockLogin = jest.fn();
  
  render(
    <AuthContext.Provider value={{ login: mockLogin }}>
      <LoginButton />
    </AuthContext.Provider>
  );
  
  screen.getByRole('button').click();
  expect(mockLogin).toHaveBeenCalledTimes(1);
});

Рекомендации по организации тестов

  • Каждый тест должен создавать собственный провайдер контекста или использовать вспомогательную функцию рендера.
  • Для нескольких контекстов удобно использовать отдельный компонент-обёртку.
  • Мокирование useContext оправдано только при невозможности обернуть компонент провайдером.
  • Тестирование должно покрывать разные значения контекста и проверять реакцию компонентов на их изменение.
  • Использование screen и ролей обеспечивает стабильность тестов и независимость от структуры DOM.

Тестирование useContext с помощью React Testing Library позволяет проверять как статические, так и динамические сценарии работы компонентов. Правильная организация провайдеров и моков делает тесты читаемыми, надёжными и масштабируемыми.