Redux mock store подходы

Одной из частых задач при тестировании приложений с использованием Redux является необходимость изоляции компонентов от реального состояния хранилища (store). Для этого широко используется подход с использованием mock store. В этом разделе рассматриваются различные способы реализации mock store, а также их преимущества и ограничения в контексте тестирования с React Testing Library.

Зачем нужно использовать mock store

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

Основные подходы к реализации mock store

1. Использование библиотеки redux-mock-store

Библиотека redux-mock-store является популярным инструментом для создания mock store в приложениях с Redux. Она позволяет легко имитировать store и отслеживать действия, которые были отправлены в ходе выполнения теста. Это упрощает тестирование компонентов, использующих Redux.

Для установки и использования redux-mock-store достаточно выполнить следующие шаги:

npm install redux-mock-store

Пример создания mock store с использованием этой библиотеки:

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';

const mockStore = configureMockStore([thunk]);

const store = mockStore({
  user: { name: 'John Doe', authenticated: true },
});

store.dispatch({ type: 'USER_LOGOUT' });

В этом примере создается mock store с начальным состоянием, а затем происходит отправка действия. Тестирование с использованием такого подхода сводится к проверке, были ли отправлены необходимые действия в ходе выполнения теста.

2. Мокаем Redux store с использованием jest

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

Пример мока для Redux store:

import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import App from './App';
import { createStore } from 'redux';

const mockReducer = (state = { user: { authenticated: true } }, action) => {
  switch (action.type) {
    case 'USER_LOGOUT':
      return { ...state, user: { authenticated: false } };
    default:
      return state;
  }
};

const store = createStore(mockReducer);

test('renders authenticated user', () => {
  const { getByText } = render(
    <Provider store={store}>
      <App />
    </Provider>
  );
  expect(getByText(/welcome, John Doe/i)).toBeInTheDocument();
});

В этом примере мы создаем mock reducer, который будет использоваться для создания Redux store. При этом для тестирования компонента App мы подключаем его через <Provider>, передавая mock store.

3. Использование @testing-library/react с redux для проверки UI

Для тестирования UI компонентов, которые взаимодействуют с Redux, также может понадобиться использование @testing-library/react совместно с mock store. Важно правильно синхронизировать изменения состояния компонента и проверку вывода на экран.

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

import { render, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import App from './App';
import configureMockStore from 'redux-mock-store';

const mockStore = configureMockStore();
const store = mockStore({ user: { authenticated: true } });

test('dispatches USER_LOGOUT action', () => {
  const { getByText } = render(
    <Provider store={store}>
      <App />
    </Provider>
  );

  const logoutButton = getByText(/log out/i);
  fireEvent.click(logoutButton);

  const actions = store.getActions();
  expect(actions).toEqual([{ type: 'USER_LOGOUT' }]);
});

В этом тесте мы проверяем, что при клике на кнопку происходит отправка действия USER_LOGOUT и что состояние компонента обновляется соответствующим образом.

4. Мокаем асинхронные действия с redux-thunk

Когда приложение использует middleware для работы с асинхронными действиями, например redux-thunk, необходимо корректно мокировать эти действия в тестах. Библиотека redux-mock-store позволяет работать с асинхронными действиями, передавая middleware.

Пример использования mock store с redux-thunk:

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { fetchData } from './actions';
import { FETCH_DATA_SUCCESS } from './types';

const mockStore = configureMockStore([thunk]);

test('fetches data and dispatches success action', async () => {
  const store = mockStore({});
  const mockData = { id: 1, name: 'Test' };

  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve(mockData),
    })
  );

  await store.dispatch(fetchData());

  const actions = store.getActions();
  expect(actions).toEqual([{ type: FETCH_DATA_SUCCESS, payload: mockData }]);
});

В этом тесте проверяется, что асинхронное действие fetchData корректно выполняется и отправляет действие с типом FETCH_DATA_SUCCESS.

Преимущества и ограничения mock store подхода

Преимущества:

  1. Изоляция теста: Использование mock store позволяет тестировать компоненты, не привязываясь к реальному состоянию Redux. Это ускоряет тесты и делает их более предсказуемыми.
  2. Легкость в реализации: Использование библиотек, таких как redux-mock-store, позволяет быстро настроить и использовать mock store в тестах.
  3. Контроль над состоянием: Mock store дает полный контроль над состоянием, что упрощает тестирование различных сценариев.

Ограничения:

  1. Не всегда реалистично: Mock store не всегда отражает все нюансы работы реального Redux store. Это может привести к недооценке сложных ситуаций, когда компонент зависит от сложных взаимодействий внутри реального store.
  2. Не тестирует реальную логику Redux: Этот подход не позволяет тестировать логику самого Redux или middleware, такие как redux-thunk. Для полноценных тестов может потребоваться использование реального store.
  3. Сложность при больших состояниях: В случае крупных состояний mock store может быть неудобным для использования, поскольку нужно вручную поддерживать актуальность структуры состояния для каждого теста.

Заключение

Использование mock store в тестах с Redux позволяет изолировать компоненты от реального состояния и действий, тем самым упрощая тестирование. Это позволяет избежать зависимости от внешних источников данных и сосредоточиться на проверке логики компонентов. Важно правильно настроить mock store для проверки различных состояний и действий, а также быть осведомленным о возможных ограничениях этого подхода.