How to Test Redux Dispatch in React

Testing Redux dispatch in React ensures that your components trigger the correct actions in response to user events. This guide provides a straightforward approach to testing dispatch functionality using Jest and React Testing Library. You will learn how to mock the useDispatch hook, check if specific actions are triggered, and verify that your React components interact with your Redux store exactly as expected.


Method 1: Mocking useDispatch Directly

The most direct way to test if a component dispatches an action is by mocking the useDispatch hook from the react-redux library. This is ideal for isolated unit tests where you only want to verify that a button click or user event triggers the dispatch function.

First, set up the Jest mock at the top of your test file:

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

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

describe('MyComponent Dispatch Tests', () => {
  let mockDispatch;

  beforeEach(() => {
    mockDispatch = jest.fn();
    useDispatch.mockReturnValue(mockDispatch);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  test('dispatches the correct action on button click', () => {
    render(<MyComponent />);
    
    const button = screen.getByRole('button', { name: /submit/i });
    fireEvent.click(button);

    // Verify that dispatch was called
    expect(mockDispatch).toHaveBeenCalledTimes(1);
    
    // Verify it was called with the correct action creator
    expect(mockDispatch).toHaveBeenCalledWith(myActionCreator());
  });
});

Method 2: Testing with redux-mock-store

If your component relies on both selecting state and dispatching actions, using redux-mock-store is a cleaner approach. This library records the history of dispatched actions, allowing you to assert against the store’s action log.

Install the package first:

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

Then, write your test using the mock store:

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

const mockStore = configureStore([]);

describe('MyComponent with Mock Store', () => {
  let store;

  beforeEach(() => {
    const initialState = { user: { loggedIn: false } };
    store = mockStore(initialState);
  });

  test('triggers the expected action in the store', () => {
    render(
      <Provider store={store}>
        <MyComponent />
      </Provider>
    );

    const button = screen.getByRole('button', { name: /login/i });
    fireEvent.click(button);

    const actions = store.getActions();
    const expectedPayload = { type: 'USER_LOGIN', payload: { userId: 1 } };
    
    expect(actions).toEqual([expectedPayload]);
  });
});

For the most reliable tests, Redux Toolkit recommends testing components with a real Redux store pre-configured with your actual reducers. This checks both the dispatch mechanism and the state changes.

You can create a custom render function to wrap your component in a real provider:

import { render, screen, fireEvent } from '@testing-library/react';
import { Provider } from 'react-redux';
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
import MyComponent from './MyComponent';

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 }) };
}

test('updates UI state after dispatching action', () => {
  renderWithRedux(<MyComponent />, { preloadedState: { user: { name: 'Guest' } } });

  const button = screen.getByRole('button', { name: /change name/i });
  fireEvent.click(button);

  // Assert against the actual state change rendered in the DOM
  expect(screen.getByText(/Hello, Admin/i)).toBeInTheDocument();
});