React Context предоставляет способ передачи данных через дерево
компонентов без необходимости явно прокидывать пропсы на каждом уровне.
Хук 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');
});
Примечание: порядок вложенности провайдеров важен только если один контекст зависит от другого. В большинстве случаев это не критично.
В сложных случаях можно замокировать 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
позволяет проверять как статические, так и динамические сценарии работы
компонентов. Правильная организация провайдеров и моков делает тесты
читаемыми, надёжными и масштабируемыми.