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();
});Method 2: Wrapping Components in a Real Store (Recommended)
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-devThen, 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' });
});