How to Mock Redux Saga in React

Testing React applications that use Redux Saga requires a reliable way to isolate side effects. This article provides a straightforward guide on how to mock Redux Saga in React using Jest and the industry-standard library redux-saga-test-plan. You will learn how to test generator functions step-by-step, mock API calls using providers, and test integration behavior without running actual asynchronous network requests.


Method 1: Step-by-Step Generator Testing (No Extra Libraries)

Because Redux Sagas are generator functions, you can test them without any external mocking libraries by manually iterating through the generator using .next(). This allows you to mock the return value of each yield effect.

The Saga

import { call, put } from 'redux-saga/effects';
import api from './api';

export function* fetchUserSaga(action) {
  try {
    const user = yield call(api.fetchUser, action.payload);
    yield put({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (error) {
    yield put({ type: 'FETCH_USER_FAILURE', message: error.message });
  }
}

The Test

import { call, put } from 'redux-saga/effects';
import { fetchUserSaga } from './sagas';
import api from './api';

describe('fetchUserSaga', () => {
  it('steps through the generator successfully', () => {
    const generator = fetchUserSaga({ payload: 1 });

    // 1. Mock the API call yield
    expect(generator.next().value).toEqual(call(api.fetchUser, 1));

    // 2. Pass fake user data into next() to simulate the API response
    const fakeUser = { id: 1, name: 'John Doe' };
    expect(generator.next(fakeUser).value).toEqual(
      put({ type: 'FETCH_USER_SUCCESS', payload: fakeUser })
    );

    // 3. Ensure the generator is finished
    expect(generator.next().done).toBe(true);
  });
});

Using the redux-saga-test-plan library is the most common and robust way to mock sagas. It lets you mock specific effects (like call) using “providers” without needing to step through every single line of the generator.

Installation

npm install --save-dev redux-saga-test-plan

Mocking API Calls with provide

You can mock specific API calls inside the saga using matchers and providers. This is highly effective for testing complex sagas.

import { expectSaga } from 'redux-saga-test-plan';
import * as matchers from 'redux-saga-test-plan/matchers';
import { throwError } from 'redux-saga-test-plan/providers';
import { fetchUserSaga } from './sagas';
import api from './api';

describe('fetchUserSaga with Test Plan', () => {
  it('mocks the API call and puts success action', () => {
    const fakeUser = { id: 1, name: 'John Doe' };

    return expectSaga(fetchUserSaga, { payload: 1 })
      .provide([
        // Mock the "call" effect to return our fake user
        [matchers.call.fn(api.fetchUser), fakeUser],
      ])
      .put({ type: 'FETCH_USER_SUCCESS', payload: fakeUser })
      .run();
  });

  it('handles errors by mocking a thrown error', () => {
    const error = new Error('User not found');

    return expectSaga(fetchUserSaga, { payload: 1 })
      .provide([
        // Mock the "call" effect to throw an error
        [matchers.call.fn(api.fetchUser), throwError(error)],
      ])
      .put({ type: 'FETCH_USER_FAILURE', message: 'User not found' })
      .run();
  });
});

Method 3: Mocking the Saga Middleware in Component Tests

When writing integration tests for React components (e.g., using React Testing Library), you may want to prevent sagas from firing completely, or mock their outputs to avoid actual network traffic during component rendering.

You can mock the saga module directly using Jest:

import React from 'react';
import { render, screen } 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 Mocked Redux Store', () => {
  it('renders without triggering real saga side-effects', () => {
    const store = mockStore({
      user: { name: 'John Doe' },
    });

    // Spy on store dispatch to see if the component triggers actions
    store.dispatch = jest.fn();

    render(
      <Provider store={store}>
        <MyComponent />
      </Provider>
    );

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