Тестирование React-компонентов, использующих Redux для управления состоянием, представляет собой важную задачу при разработке приложений. Enzyme предоставляет удобные инструменты для тестирования компонентов, однако, чтобы провести полноценное тестирование компонентов, работающих с Redux, необходимо использовать моки для Redux Store. Это позволяет изолировать тестируемые компоненты от реального хранилища и тестировать их логику отдельно от глобального состояния приложения.
Мокирование Redux Store требуется для того, чтобы:
shallow с мокированным Redux storeEnzyme предлагает метод shallow, который позволяет
рендерить компоненты на одном уровне, не включая их дочерние компоненты.
Это подходит для тестирования компонентов, использующих Redux, так как
позволяет контролировать только один компонент без необходимости
тестировать весь компонентный дерево.
Для того чтобы мокировать Redux Store, нужно использовать обёртку для
компонента, которая позволяет передать нужный store. Для
этого часто используется библиотека redux-mock-store,
которая упрощает создание моков для Redux Store.
Пример настройки теста с shallow и мокированным Redux
Store:
import React from 'react';
import { shallow } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import MyComponent from './MyComponent';
const mockStore = configureMockStore();
const initialState = {
user: { name: 'John Doe', loggedIn: true },
};
const store = mockStore(initialState);
describe('MyComponent', () => {
it('должен рендерить компонент с мокированным состоянием', () => {
const wrapper = shallow(
<Provider store={store}>
<MyComponent />
</Provider>
);
expect(wrapper).toMatchSnapshot();
});
});
В этом примере создаётся мокированный Redux Store с помощью
redux-mock-store и передаётся в компонент через
Provider. Компонент MyComponent получает из
стора состояние пользователя и может быть протестирован на основе этого
состояния.
mount для полноценного рендерингаЕсли необходимо протестировать компонент, который взаимодействует с
дочерними компонентами или использует хук useDispatch, то
следует использовать mount вместо shallow.
Метод mount рендерит полный DOM, включая всех детей
компонента, что даёт возможность проверить взаимодействие с реальным DOM
и более сложные кейсы.
Пример с использованием mount:
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import MyComponent from './MyComponent';
const mockStore = configureMockStore();
const initialState = {
user: { name: 'John Doe', loggedIn: true },
};
const store = mockStore(initialState);
describe('MyComponent', () => {
it('должен правильно работать с действиями Redux', () => {
const wrapper = mount(
<Provider store={store}>
<MyComponent />
</Provider>
);
wrapper.find('button').simulate('click');
expect(store.getActions()).toEqual([{ type: 'USER_LOGOUT' }]);
});
});
Здесь используется mount, чтобы рендерить компонент и
проверять взаимодействие с действиями Redux, такими как вызов
store.dispatch. Такой подход полезен, когда важно
проверить, как компонент инициирует изменения в Redux Store.
Иногда необходимо протестировать только определённые действия, которые передаются в компонент, без реального использования всех данных из Redux Store. В таких случаях можно замокировать только необходимые части хранилища, включая действия.
Пример тестирования компонента с замокированным действием:
import React from 'react';
import { shallow } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import MyComponent from './MyComponent';
const mockStore = configureMockStore();
const store = mockStore({
user: { name: 'John Doe', loggedIn: true },
});
describe('MyComponent', () => {
it('должен вызывать действие при клике на кнопку', () => {
const wrapper = shallow(
<Provider store={store}>
<MyComponent />
</Provider>
);
wrapper.find('button').simulate('click');
const actions = store.getActions();
expect(actions).toEqual([{ type: 'USER_LOGOUT' }]);
});
});
Здесь создаётся мокированное хранилище, но важно, что мы можем тестировать именно вызов действия при клике на кнопку, не влияя на реальное состояние хранилища.
redux-mock-store и асинхронных тестовКогда компоненты взаимодействуют с асинхронными действиями, например,
с запросами на сервер, необходимо учесть асинхронную природу этих
операций. redux-mock-store позволяет использовать
асинхронные действия, мокируя их поведение.
Пример асинхронного теста с использованием мокированного Redux Store:
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import MyComponent from './MyComponent';
const mockStore = configureMockStore([thunk]);
const store = mockStore({ user: { name: 'John Doe', loggedIn: true } });
describe('MyComponent', () => {
it('должен корректно обрабатывать асинхронные действия', async () => {
const wrapper = mount(
<Provider store={store}>
<MyComponent />
</Provider>
);
wrapper.find('button').simulate('click');
await new Promise(resolve => setImmediate(resolve)); // ждем асинхронных действий
const actions = store.getActions();
expect(actions).toContainEqual({ type: 'FETCH_USER_SUCCESS', payload: { name: 'John Doe' } });
});
});
В данном примере с использованием redux-thunk мы
мокируем асинхронное действие, проверяя, что оно было вызвано и привело
к нужному состоянию.
redux-mock-store, которая
имитирует вызовы и позволяет проверять, какие именно действия были
диспатчены.setImmediate, чтобы дождаться завершения всех асинхронных
процессов перед проверкой результатов.Мокирование Redux Store с помощью Enzyme является мощным инструментом для тестирования React-компонентов. Это позволяет изолировать компоненты и тестировать их логику независимо от внешнего состояния приложения.