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);
});
});Method 2: Asserting with Redux Saga Test Plan (Recommended)
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-planMocking 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();
});
});