How to Mock Redux in React

Testing React components that rely on Redux requires a strategy to handle the global state store without running the entire application. This article provides a straightforward guide on how to mock Redux in React using Jest and React Testing Library. You will learn how to mock Redux hooks like useSelector and useDispatch for isolated unit tests, how to create a custom render function with a real Redux provider for integration testing, and how to use the redux-mock-store library.

Method 1: Mocking Redux Hooks with Jest

If you want to write isolated unit tests without configuring a Redux store, you can mock the react-redux hooks directly. This approach is lightweight and keeps your tests simple.

import { render, screen, fireEvent } from '@testing-library/react';
import { useSelector, useDispatch } from 'react-redux';
import MyComponent from './MyComponent';

// Mock the react-redux hooks
jest.mock('react-redux', () => ({
  ...jest.requireActual('react-redux'),
  useSelector: jest.fn(),
  useDispatch: jest.fn(),
}));

test('should render mock state and dispatch action', () => {
  const mockDispatch = jest.fn();
  useDispatch.mockReturnValue(mockDispatch);
  
  // Define what the selector should return for this test
  useSelector.mockImplementation((selector) =>
    selector({ user: { name: 'John Doe' } })
  );

  render(<MyComponent />);

  // Assert that the component displays the mocked state
  expect(screen.getByText('John Doe')).toBeInTheDocument();

  // Simulate user interaction to trigger dispatch
  fireEvent.click(screen.getByRole('button'));
  expect(mockDispatch).toHaveBeenCalled();
});

The Redux team recommends wrapping your test components in a real Redux <Provider> configured with a test store. This ensures your components interact with the state exactly as they do in production, making your tests more robust.

You can create a reusable helper function in a file named test-utils.js:

import React from 'react';
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice'; // Import your actual reducer

export function renderWithRedux(
  ui,
  {
    preloadedState,
    store = configureStore({
      reducer: { user: userReducer },
      preloadedState,
    }),
    ...renderOptions
  } = {}
) {
  function Wrapper({ children }) {
    return <Provider store={store}>{children}</Provider>;
  }
  return { store, ...render(ui, { wrapper: Wrapper, ...renderOptions }) };
}

Now you can write clean integration tests by passing the custom initial state directly to the helper:

import { screen } from '@testing-library/react';
import { renderWithRedux } from './test-utils';
import MyComponent from './MyComponent';

test('renders with preloaded Redux state', () => {
  renderWithRedux(<MyComponent />, {
    preloadedState: { user: { name: 'Jane Doe' } },
  });

  expect(screen.getByText('Jane Doe')).toBeInTheDocument();
});

Method 3: Mocking Redux with redux-mock-store

If your test only needs to verify that specific actions are dispatched and you do not need the state to update dynamically during the test execution, you can use the redux-mock-store library.

First, install the library:

npm install redux-mock-store --save-dev

Then, configure the mock store in your test file:

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

const mockStore = configureMockStore();

test('should dispatch action on button click', () => {
  const initialState = { user: { name: 'Alice' } };
  const store = mockStore(initialState);
  
  render(
    <Provider store={store}>
      <MyComponent />
    </Provider>
  );

  // Verify the initial state rendering
  expect(screen.getByText('Alice')).toBeInTheDocument();

  // Trigger dispatch action
  fireEvent.click(screen.getByRole('button', { name: /update name/i }));

  // Check if the expected action was dispatched to the store
  const actions = store.getActions();
  expect(actions[0]).toEqual({ type: 'user/updateName', payload: 'New Name' });
});