Структура Redux приложения в тестах

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

Введение в тестирование с Redux

При тестировании компонентов, использующих Redux, важно разделить два аспекта:

  1. Тестирование компонента без Redux.
  2. Тестирование компонента с Redux, где необходимо взаимодействовать с состоянием хранилища.

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

Интеграция Redux в тестах с Enzyme

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

import { Provider } from 'react-redux';
import { shallow } from 'enzyme';
import MyComponent from './MyComponent';
import configureStore from 'redux-mock-store';

// Создание мокового хранилища
const mockStore = configureStore();
const store = mockStore({ key: 'value' });

// Обертка компонента в Provider для передачи состояния
const wrapper = shallow(
  <Provider store={store}>
    <MyComponent />
  </Provider>
);

Моки и фикстуры для тестов Redux

При тестировании с Redux часто используются моки для состояния и действий. Это позволяет изолировать компонент от реальной логики Redux и сосредоточиться на тестировании взаимодействия с состоянием.

Для создания моков состояния и действий можно использовать библиотеку redux-mock-store, которая позволяет создать фальшивое хранилище, имитирующее реальную работу с Redux.

import { createStore } from 'redux';
import reducer from './reducer';
import MyComponent from './MyComponent';

// Мок состояния
const initialState = {
  user: {
    name: 'John',
    age: 30,
  },
};

// Создание хранилища
const store = createStore(reducer, initialState);

// Тестирование компонента с моковым состоянием
const wrapper = shallow(
  <Provider store={store}>
    <MyComponent />
  </Provider>
);

Проверка состояния компонента

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

Для этого можно использовать метод find из Enzyme для поиска элементов на основе определённых классов или тегов. Важно проверять не только сам компонент, но и его внутреннее состояние, которое зависит от данных в хранилище.

it('отображает имя пользователя', () => {
  const wrapper = shallow(
    <Provider store={store}>
      <MyComponent />
    </Provider>
  );

  expect(wrapper.find('.user-name').text()).toBe('John');
});

Проверка взаимодействий с хранилищем

Для того чтобы проверить, как компонент взаимодействует с хранилищем Redux (например, как он dispatch’ит действия), нужно убедиться, что соответствующие действия вызываются при определённых событиях. В Enzyme для этого можно использовать методы simulate, dispatch и проверку вызова функции действия.

Пример теста, который проверяет вызов действия при клике на кнопку:

it('вызывает action при клике на кнопку', () => {
  const mockDispatch = jest.fn();
  const wrapper = shallow(
    <Provider store={store}>
      <MyComponent dispatch={mockDispatch} />
    </Provider>
  );

  wrapper.find('.button').simulate('click');
  expect(mockDispatch).toHaveBeenCalledWith({ type: 'SOME_ACTION' });
});

Использование connect в тестах

Если компонент использует connect для подключения к Redux, то важно правильно передавать состояние и действия через mapStateToProps и mapDispatchToProps. В тестах можно использовать моковые реализации этих функций, чтобы изолировать компонент от сложной логики Redux.

Пример использования моковых функций:

const mapStateToProps = jest.fn(() => ({ user: { name: 'John' } }));
const mapDispatchToProps = jest.fn();

it('передает правильные пропсы в компонент', () => {
  const wrapper = shallow(
    <MyComponent 
      mapStateToProps={mapStateToProps}
      mapDispatchToProps={mapDispatchToProps}
    />
  );

  expect(mapStateToProps).toHaveBeenCalledWith({ user: { name: 'John' } });
});

Тестирование с асинхронными действиями

Тестирование Redux с асинхронными действиями требует особого внимания, поскольку компоненты могут обновляться на основе асинхронных запросов, таких как запросы API. Для таких случаев часто используют middleware, как redux-thunk, для тестирования асинхронных экшенов.

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

it('вызывает асинхронное действие', async () => {
  const mockAsyncAction = jest.fn().mockResolvedValue({ type: 'ASYNC_ACTION' });
  
  const wrapper = shallow(
    <MyComponent asyncAction={mockAsyncAction} />
  );
  
  await wrapper.instance().someAsyncMethod();
  
  expect(mockAsyncAction).toHaveBeenCalled();
});

Работа с redux-saga в тестах

Если в приложении используется redux-saga, то для тестирования саг также потребуется специфический подход. Для тестирования саг можно использовать redux-saga-test-plan, который позволяет симулировать эффекты саг и проверять, как они работают с действиями.

Пример теста для саги:

import { runSaga } from 'redux-saga';
import { fetchUserSaga } from './sagas';
import * as api from './api';

it('тестирует сагу', async () => {
  const dispatchedActions = [];
  const mockApi = jest.spyOn(api, 'fetchUser').mockResolvedValue({ name: 'John' });

  const fakeStore = {
    dispatch: (action) => dispatchedActions.push(action),
  };

  await runSaga(fakeStore, fetchUserSaga).toPromise();

  expect(dispatchedActions).toEqual([{ type: 'USER_FETCH_SUCCEEDED', user: { name: 'John' } }]);
});

Заключение

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