How to Test Redux Reducers in React

Testing Redux reducers is a crucial step in ensuring your React application’s state transitions behave predictably. Because reducers are pure functions, writing unit tests for them is straightforward and does not require rendering React components or mocking complex network requests. This guide provides a direct, step-by-step approach to testing Redux reducers using Jest, covering initial state verification and action handling with clear code examples.

The Core Concept of Reducer Testing

A Redux reducer is a pure function that takes the current state and an action as arguments, and returns a new state:

(state, action) => newState

Because of this purity, testing a reducer simply requires passing a specific state and action into the function, and asserting that the returned state matches your expectations. You do not need to set up a Redux store or use React Testing Library for these tests.

Step 1: The Reducer Under Test

Consider this simple counter reducer that manages an integer count:

// counterReducer.js
const initialState = { count: 0 };

export default function counterReducer(state = initialState, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

Step 2: Testing the Initial State

The first test should verify that the reducer returns its default initial state when it is initialized with an undefined state and an empty action.

import counterReducer from './counterReducer';

describe('counterReducer', () => {
  it('should return the initial state when passed undefined', () => {
    const result = counterReducer(undefined, { type: '@@INIT' });
    expect(result).toEqual({ count: 0 });
  });
});

Step 3: Testing Action Handlers

Next, write test cases for each action creator or action type that the reducer handles. You provide a mock initial state, dispatch the action, and assert the output.

Testing the INCREMENT Action

it('should handle INCREMENT', () => {
  const startingState = { count: 3 };
  const action = { type: 'INCREMENT' };
  
  const result = counterReducer(startingState, action);
  
  expect(result).toEqual({ count: 4 });
});

Testing the DECREMENT Action

it('should handle DECREMENT', () => {
  const startingState = { count: 5 };
  const action = { type: 'DECREMENT' };
  
  const result = counterReducer(startingState, action);
  
  expect(result).toEqual({ count: 4 });
});

Testing Unhandled Actions

It is also important to verify that the reducer ignores action types it does not care about and returns the current state unchanged.

it('should return the current state for unknown action types', () => {
  const startingState = { count: 10 };
  const action = { type: 'SOME_UNKNOWN_ACTION' };
  
  const result = counterReducer(startingState, action);
  
  expect(result).toEqual({ count: 10 });
});

Best Practices for Reducer Tests