Hire the author: Larry K

Writing React unit tests for Asynchronous functions might be a daunting task for most front-end developers/engineers. While working as a fronted-engineer I had trouble testing Asynchronous Redux actions . This was mostly because they require one to mock API calls. The concept of writing mock tests was new to me Given that I came from a back-end background.

As expected I searched on how to test Asynchronous Redux actions on the internet. The tutorials only covered “GET” requests but ignored “POST” requests for asynchronous actions. I am going to do a tutorial on how to test Asynchronous Redux actions  for both GET and POST requests. You can find the example code for this tutorial here .

Panda gif
source gify.com

Glossary

  • Redux Redux is a predictable state container for JavaScript apps.
  • Jest – Jest is a JavaScript test runner for creating, running, and structuring tests.

What are asynchronous Redux actions creators?

Before we understand what asynchronous actions are, it’s crucial to get to know what a Redux action is.  Actions,are plain Javascript objects that contain;

  1. The action type which the action creators will perform .
  2. A payload which the data that the action creators will dispatch to the store.

Below is an example of an object.

{ type: ADD_TODO_SUCCESS, payload: { item: 'Todo item' } }
{ type: ADD_TODO_FAILURE, payload: {error: 'Bad item'} }

Asynchronous action creators

An action creator is a function that creates actions and has no side effects. In order to use asynchronous action creators in Redux we are required to use Redux Thunk middleware which comes in a separate package called Redux-thunk.  This middleware enables our asynchronous action creator to return a function instead of an object. Testing asynchronous code is slightly different than regular functions. I am going to use a Todo app for demonstration purposes. Most of the actions in our example will contain API calls and action creators as shown in the example  below.

Actions Types and Action Creators

// ACTION TYPES
export const API_REQUEST = 'API_REQUEST';
export const GET_TODO_SUCCESS = 'GET_TODO_SUCCESS';
export const GET_TODO_FAIL = 'GET_TODO_FAIL';
export const ADD_TODO_SUCCESS = 'ADD_TODO_SUCCESS';
export const ADD_TODO_FAIL = 'ADD_TODO_FAIL';
//ACTION CREATORS
export const apiRequestAction = () => ({
type: API_REQUEST
});
export const getTodoSuccess = todos => ({
type: API_REQUEST,
payload: todos
});
export const getTodosFail = error => ({
type: GET_TODO_FAIL,
payload: error
})
export const addTodoSuccess = todo => ({
type: ADD_TODO_SUCCESS,
payload: todo
})
export const addTodoFail = error => (
{
type: ADD_TODO_FAIL,
payload: error
}
)
view raw actions.js hosted with ❤ by GitHub

The code snippet above shows a list of action types and action creators that are going to be used in our Todo actions.

 Next we are going to create asynchronous  functions that will make the calls to the API and dispatch the actions to the store after an asynchronous API request has been handled as shown on the code below.

Asynchronous API calls

import axios from 'axios';
import * as actions from './actions';
export const getTodos = () => (dispatch) => {
dispatch(actions.apiRequest());
axios.get('api/todos').then(res => {
dispatch(actions.getTodoSuccess(res.data));
return res;
})
.catch(error => {
dispatch(actions.getTodosFail(error));
return error
});
};
export const addTodo = () => (dispatch) => {
dispatch(actions.apiRequest());
axios.post('api/todos').then(res => {
dispatch(actions.addTodoSuccess(res.data));
return res;
})
.catch(error => {
dispatch(actions.addTodoFail(error));
return error
})
}
view raw todos.js hosted with ❤ by GitHub

TESTING.

What we want to test here is when we call any of the Asynchronous Redux actions above it is going to dispatch the action together with the associated payload.  We are going to use axios-mock-adapter to mock our API calls and return mock data as the API response and finally the test case will involve checking if the action payload is what we expect to see. All dependencies in the test should be installed as dev-dependacies. The test case for getting a Todo will be as follows.

TESTS FOR GET TODOS

import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import * as actions from './actions';
const middleware = [thunk];
const mockStore = configureMockStore(middleware);
const mock = new MockAdapter(axios);
const store = mockStore();
describe('getTodos actions', () => {
beforeEach(() => {
store.clearActions();
});
it('dispatches GET_TODO_SUCCESS after a successfull API requets', () => {
mock.onGet('api/todos').reply(200, { response: [{ item: 'item1' }, { item: 'item2' }] })
store.dispatch(actions.getTodos()).then(() => {
let expectedActions = [
{ type: 'API_REQUEST' },
{
type: 'GET_TODOS_SUCCESS',
payload: [{ item: 'item1' }, { item: 'item2' }]
}
]
expect(store.getActions()).toEqual(expectedActions)
});
});
it('dispatches GET_TODO_FAILURE after a FAILED API requets', () => {
mock.onGet('api/todos').reply(400, { error: { message: 'error message' } });
store.dispatch(actions.getTodos()).then(() => {
let expectedActions = [
{ type: 'API_REQUEST' },
{
type: 'GET_TODOS_FAIL',
payload: { error: { message: 'error message' } }
}
]
expect(store.getActions()).toEqual(expectedActions)
});
});
});
view raw test.actions.js hosted with ❤ by GitHub

From the test case above we have imported configureStore and thunk to test our asynchronous method. We have also imported the MockAdapter to mock our API calls and finally initialized the store, middleware and the thunk. The test case involves testing if the asynchronous actions will dispatch the actions to the store that’s all when it comes to testing GET API calls. Next we test the POST HTTP API calls. 

TESTS FOR POST TODOS.

describe('post Todos actions', () => {
beforeEach(() => {
store.clearActions();
});
it('dispatches POST_TODO_SUCCESS after a successfull API requets', () => {
mock.onPost('api/todos').reply(201, { response: { item: 'item1' } })
store.dispatch(actions.getTodos()).then(() => {
let expectedActions = [
{ type: 'API_REQUEST' },
{ type: 'POST_TODOS_SUCCESS', payload: { item: 'item1' } }
]
expect(store.getActions()).toEqual(expectedActions)
});
});
it('dispatches POST_TODO_FAILURE after a FAILED API requets', () => {
mock.onPost('api/todos').reply(400, { error: { message: 'error message' } });
store.dispatch(actions.getTodos()).then(() => {
let expectedActions = [
{ type: 'API_REQUEST' },
{
type: 'POST_TODOS_FAIL',
payload: { error: { message: 'error message' } }
}
]
expect(store.getActions()).toEqual(expectedActions)
});
});
});
view raw test.actions.js hosted with ❤ by GitHub

The test case for the POST request associated with the asynchronous Redux actions is as shown above. Here we also test that the store will be updated with the relevant actions following an asynchronous Redux action call. 

Learning Strategy

Before coming to this solution I questioned the viability of having to write unit tests for asynchronous Redux actions . My argument was testing the reducers would cover for the actions as well. This was not true because in one of the project I was working on the test coverage was low because the actions were not tested. This made me search the internet for solutions on how to test asynchronous Redux actions.

I came a cross multiple tutorials on this subject but most only covered GET request methods and ignored POST request methods. They also ignored the most important aspect on how to structure action creators to make them more testable by separating the action creators and the API calls. My learning strategy in solving this problem was to separate the action creators from the API calls. After that I mocked the response of the API calls using the axios mock adapter.

Learning Tools

  1. Redux test documentation.
  2. Axios-mock-adapter documentation.

Reflective Analysis.

To make the code testable it should be broken down into small units.  When testing Asynchronous Redux actions one should separate the action creators from the API calls. This makes the action creators more testable. Other than making real API calls one should mock the response data from the APIs.

Conclusion

In this article we have learnt how we can test asynchronous Redux actions using jest. We have also understood how to structure action creators to be more testable. Moving forward we can learn how to structure reducers and test them as well.

That’s it, I hope you have learnt something new. If you found this article helpful kindly share it to help others as well. You can find more exiting learning materials here. Cheers!!!!

Hire the author: Larry K