Мокирование Redux store

Тестирование React-компонентов, использующих Redux для управления состоянием, представляет собой важную задачу при разработке приложений. Enzyme предоставляет удобные инструменты для тестирования компонентов, однако, чтобы провести полноценное тестирование компонентов, работающих с Redux, необходимо использовать моки для Redux Store. Это позволяет изолировать тестируемые компоненты от реального хранилища и тестировать их логику отдельно от глобального состояния приложения.

Зачем мокировать Redux store

Мокирование Redux Store требуется для того, чтобы:

  • Изолировать тестируемые компоненты от зависимостей, которые не имеют отношения к логике компонента.
  • Упростить тестирование, предоставив компоненты с заранее определёнными значениями состояния.
  • Убедиться в правильности работы компонента с Redux Store, проверяя поведение без использования реального хранилища, которое может изменяться в зависимости от множества факторов.

Использование shallow с мокированным Redux store

Enzyme предлагает метод 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 Store

  • Мокирование состояний: Важно заранее определить начальное состояние, которое будет использоваться в тестах. Это помогает создать стабильную среду для проверки компонента.
  • Мокирование действий: Если компонент вызывает асинхронные действия, можно использовать моки для их тестирования. Для этого используется библиотека redux-mock-store, которая имитирует вызовы и позволяет проверять, какие именно действия были диспатчены.
  • Асинхронность: При тестировании асинхронных операций важно учитывать задержки и использовать промисы или setImmediate, чтобы дождаться завершения всех асинхронных процессов перед проверкой результатов.

Мокирование Redux Store с помощью Enzyme является мощным инструментом для тестирования React-компонентов. Это позволяет изолировать компоненты и тестировать их логику независимо от внешнего состояния приложения.