Провайдеры контекста в тестах

Тестирование компонентов, использующих контексты в React, требует особого подхода. Контексты предоставляют глобальные данные, которые могут быть доступны для любого компонента в дереве компонентов. В процессе тестирования необходимо правильно настраивать эти контексты, чтобы гарантировать корректное поведение тестируемых компонентов. В этом разделе рассмотрены способы интеграции контекстов в тесты с использованием React Testing Library.

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

Контекст в React позволяет передавать данные по дереву компонентов, минуя промежуточные компоненты. Для использования контекста обычно создается специальный провайдер, который предоставляет данные. В React Testing Library, для правильного тестирования компонентов, использующих контекст, необходимо обернуть их в соответствующие провайдеры.

Пример создания контекста:

import React, { createContext, useContext, useState } 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);

В данном примере создается контекст ThemeContext и провайдер ThemeProvider, который передает состояние темы и функцию для переключения темы в дерево компонентов.

Подготовка теста с контекстом

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

Пример теста, где используется ThemeContext:

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

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

  return (
    <div>
      <p>{theme} theme</p>
      <button onCl ick={toggleTheme}>Toggle Theme</button>
    </div>
  );
};

test('тема переключается при клике на кнопку', () => {
  render(
    <ThemeProvider>
      <ThemedComponent />
    </ThemeProvider>
  );

  expect(screen.getByText('light theme')).toBeInTheDocument();

  fireEvent.click(screen.getByText('Toggle Theme'));

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

В этом примере ThemedComponent использует контекст ThemeContext для отображения текущей темы и переключения ее при клике на кнопку. В тесте мы оборачиваем компонент в ThemeProvider и проверяем, что тема меняется при клике.

Обертки для контекста

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

const renderWithTheme = (ui) => {
  return render(
    <ThemeProvider>
      {ui}
    </ThemeProvider>
  );
};

Теперь все тесты, использующие ThemeContext, могут использовать renderWithTheme для упрощения процесса рендеринга компонентов с нужным контекстом.

test('компонент отображает правильную тему', () => {
  renderWithTheme(<ThemedComponent />);
  
  expect(screen.getByText('light theme')).toBeInTheDocument();
});

Множественные провайдеры

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

Пример с несколькими контекстами:

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

const LanguageContext = createContext();
const AuthContext = createContext();

export const LanguageProvider = ({ children }) => {
  const [language, setLanguage] = useState('en');
  
  const changeLanguage = (lang) => setLanguage(lang);

  return (
    <LanguageContext.Provider value={{ language, changeLanguage }}>
      {children}
    </LanguageContext.Provider>
  );
};

export const AuthProvider = ({ children }) => {
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  const login = () => setIsAuthenticated(true);
  const logout = () => setIsAuthenticated(false);

  return (
    <AuthContext.Provider value={{ isAuthenticated, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

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

const renderWithProviders = (ui) => {
  return render(
    <LanguageProvider>
      <AuthProvider>
        {ui}
      </AuthProvider>
    </LanguageProvider>
  );
};

Тесты будут выглядеть следующим образом:

test('компонент отображает правильный язык и статус аутентификации', () => {
  renderWithProviders(<YourComponent />);

  expect(screen.getByText('Current Language: en')).toBeInTheDocument();
  expect(screen.getByText('Authenticated: false')).toBeInTheDocument();
});

Работа с асинхронными контекстами

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

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

const UserContext = createContext();

export const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser().then(data => setUser(data));
  }, []);

  return (
    <UserContext.Provider value={user}>
      {children}
    </UserContext.Provider>
  );
};

const fetchUser = async () => {
  const response = await fetch('/api/user');
  return response.json();
};

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

test('загруженные данные пользователя отображаются', async () => {
  renderWithProviders(<YourComponent />);

  expect(await screen.findByText('Loading...')).toBeInTheDocument();

  expect(await screen.findByText('User: John Doe')).toBeInTheDocument();
});

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

Заключение

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