How to Test Redux Actions in React
Testing Redux actions is a crucial step in ensuring your React application’s state management behaves predictably. This article provides a straightforward guide on how to test both synchronous and asynchronous Redux actions using Jest and a mock Redux store. You will learn how to set up your test suite, write assertions for basic action creators, and handle asynchronous side effects like API calls.
Testing Synchronous Actions
Synchronous action creators in Redux are pure functions that return a plain JavaScript object. Because they have no side effects, testing them is highly straightforward. You only need to call the action creator with specific arguments and assert that it returns the expected action object.
Here is an example of a synchronous action creator:
// actions.js
export const addTodo = (text) => ({
type: 'ADD_TODO',
payload: text,
});To test this action creator using Jest, write the following test case:
// actions.test.js
import { addTodo } from './actions';
describe('actions', () => {
it('should create an action to add a todo', () => {
const text = 'Finish writing test';
const expectedAction = {
type: 'ADD_TODO',
payload: text,
};
expect(addTodo(text)).toEqual(expectedAction);
});
});Testing Asynchronous Actions (Thunks)
Asynchronous action creators, often created using middleware like
redux-thunk, involve side effects such as fetching data
from an API. Testing these requires mocking both the Redux store and the
HTTP requests.
To test thunks, you need to install redux-mock-store to
capture dispatched actions, and mock your network requests using Jest’s
built-in mocking utilities.
Here is an example of an asynchronous action creator:
// asyncActions.js
export const fetchUsersSuccess = (users) => ({
type: 'FETCH_USERS_SUCCESS',
payload: users,
});
export const fetchUsers = () => {
return (dispatch) => {
return fetch('/api/users')
.then((response) => response.json())
.then((users) => dispatch(fetchUsersSuccess(users)));
};
};To test this asynchronous action, configure a mock store with the
redux-thunk middleware and mock the global
fetch API:
// asyncActions.test.js
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { fetchUsers } from './asyncActions';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe('async actions', () => {
beforeEach(() => {
global.fetch = jest.fn();
});
afterEach(() => {
jest.restoreAllMocks();
});
it('creates FETCH_USERS_SUCCESS when fetching users has been done', () => {
const mockUsers = [{ id: 1, name: 'John Doe' }];
// Mock the fetch API response
global.fetch.mockResolvedValueOnce({
json: () => Promise.resolve(mockUsers),
});
const expectedActions = [
{ type: 'FETCH_USERS_SUCCESS', payload: mockUsers }
];
const store = mockStore({ users: [] });
return store.dispatch(fetchUsers()).then(() => {
expect(store.getActions()).toEqual(expectedActions);
});
});
});By verifying that your synchronous action creators return the correct objects and that your asynchronous thunks dispatch the expected sequence of actions, you can guarantee the integrity of your React-Redux application’s data flow.