How to Mock Redux Thunk in React
Testing asynchronous actions in React applications often requires
mocking Redux Thunk to isolate component behavior and verify state
changes. This article provides a straightforward, step-by-step guide on
how to mock Redux Thunk using Jest and the redux-mock-store
library, enabling you to write clean, reliable unit tests for your
asynchronous action creators.
Prerequisites
To mock Redux Thunk, you will need to install
redux-mock-store. This library allows you to test Redux
action creators and middleware without relying on the actual Redux
store.
Run the following command in your terminal:
npm install --save-dev redux-mock-store redux-thunkStep 1: Configure the Mock Store
To test thunks, you must apply the redux-thunk
middleware to your mock store. This ensures that when you dispatch an
asynchronous action, the mock store knows how to process the function
returned by the thunk.
Create your mock store configuration like this:
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);Step 2: Create the Thunk Action to Test
Assume you have an asynchronous action creator (thunk) that fetches data from an API and dispatches success or failure actions:
// actions.js
export const fetchUserData = (userId) => async (dispatch) => {
dispatch({ type: 'FETCH_USER_REQUEST' });
try {
const response = await fetch(`/api/user/${userId}`);
const data = await response.json();
dispatch({ type: 'FETCH_USER_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_USER_FAILURE', error: error.message });
}
};Step 3: Write the Unit Test
To test the thunk, mock the global fetch API (or your
HTTP client, such as Axios), dispatch the thunk action to the mock
store, and assert that the expected actions were dispatched in the
correct order.
// actions.test.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { fetchUserData } from './actions';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('fetchUserData Thunk', () => {
beforeEach(() => {
// Reset fetch mocks before each test
global.fetch = jest.fn();
});
it('dispatches FETCH_USER_REQUEST and FETCH_USER_SUCCESS on success', async () => {
const mockUser = { id: '123', name: 'John Doe' };
// Mock successful API response
global.fetch.mockResolvedValueOnce({
json: jest.fn().mockResolvedValueOnce(mockUser),
});
const expectedActions = [
{ type: 'FETCH_USER_REQUEST' },
{ type: 'FETCH_USER_SUCCESS', payload: mockUser }
];
const store = mockStore({});
// Dispatch the thunk and wait for it to resolve
await store.dispatch(fetchUserData('123'));
// Verify the dispatched actions
expect(store.getActions()).toEqual(expectedActions);
});
it('dispatches FETCH_USER_REQUEST and FETCH_USER_FAILURE on error', async () => {
// Mock API error
global.fetch.mockRejectedValueOnce(new Error('Network Error'));
const expectedActions = [
{ type: 'FETCH_USER_REQUEST' },
{ type: 'FETCH_USER_FAILURE', error: 'Network Error' }
];
const store = mockStore({});
await store.dispatch(fetchUserData('123'));
expect(store.getActions()).toEqual(expectedActions);
});
});Conclusion
By combining redux-mock-store with
redux-thunk middleware, you can easily execute and test
asynchronous flows. This approach allows you to inspect the list of
dispatched actions and assert that your application behaves correctly
during both successful and failed network requests.