Контекст API — это мощный инструмент, встроенный в React, который позволяет передавать данные через дерево компонентов, не прибегая к пропсами на каждом уровне. Он полезен, когда необходимо делиться состоянием или функциями между компонентами, не беспокоясь о явной передаче пропсов через каждый уровень иерархии.
Контекст 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 полезен для реализации таких паттернов, как глобальное состояние, настройки темы, а также для работы с аутентификацией и локализацией.
Тестирование компонентов, использующих контекст, может представлять определённые трудности. Основные проблемы заключаются в том, что контекст может быть глубоко вложен, и необходимо обеспечить, чтобы тестируемые компоненты получали правильное значение контекста.
Для тестирования таких компонентов важно, чтобы тесты могли
использовать реальное значение контекста. Для этого существуют несколько
подходов, включая использование render-функции из
популярных библиотек, таких как @testing-library/react, или
прямое использование mount из библиотеки Enzyme.
Enzyme позволяет тестировать компоненты, использующие Context API,
путём обёртывания тестируемого компонента в соответствующий
Provider и передавая необходимое значение контекста.
Создадим простое приложение, использующее контекст для передачи текущего языка:
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>
);
}
Вместо использования @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, чтобы он имел доступ к значению
контекста.
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 позволяет нам имитировать изменение значений
контекста и убедиться, что компоненты, которые его потребляют, реагируют
на эти изменения.
Если контекст передаёт функции (например, 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. Замокированные функции и манипуляции с контекстом
помогают детально тестировать взаимодействие между компонентами и их
зависимости.