React: Jest Unit Testing with redux-axios-middleware

Motivation and sad story: When I was a newbie in React and Redux, I was too lazy to figure out how to do unit testing. Every time I call the backend API, I would console log my API response. It was very very very… very slow. Evnetually, I took the courage to google it. But back then, there weren’t copy-and-paste-able solution, so I had to understand redux middleware, thunk, promise, etc—things that I should have known before I coded a single line of react. Hopefully this tutorial saves some poor soul some times in understanding and setting up the unit testing.

Table of Contents

Overview

In order for this to work, you need the following:

  1. Setting up Jest, as detailed here. Note that if you used react-create-app, it is used by default
  2. Getting a mock store, which serves as our fake redux store
  3. Applying the axios middleware to the mock store
  4. Lastly, use the expect function to verify the success of the API call

Code and Explanation

import configureMockStore from 'redux-mock-store';
import axios from 'axios';
import expect from 'expect'
import axiosMiddleware from 'redux-axios-middleware';

const client = axios.create({
    baseURL: your_base_url_here,
    responseType: 'json'
});

let middlewares = [axiosMiddleware(client)];
const mockStore = configureMockStore(middlewares);
const store = mockStore();

test('Test if axios middleware successfully get a response from the api', () => {
    const expectedAction = { type: YOUR_ACTION_TYPE + '_SUCCESS'};
    return store.dispatch(YOUR_ACTION_CREATOR()).then(() => {
        expect(
            store.getActions().find(a => a.type === expectedAction.type)
        ).toBeDefined();
    });
});

We first set up the mock store and the middleware. Middlewares are essentially things that users can interject between an action and a reducer to do extra work. (A deeper understanding of middlewares can be found at here). In axios’ case, we perform an api call.

We first define the api url:


const client = axios.create({
    baseURL: your_base_url_here,
    responseType: 'json'
});

We then apply the middlewares. Unlike the actual redux store, we simply put all the middlewares we want in an array and pass the array to configureMockStore. Every time we want a fresh new store, we call mockStore()


const middlewares = [axiosMiddleware(client)];
const mockStore = configureMockStore(middlewares);
const store = mockStore();

We define the test case. This is just the general Jest test case:


test('Test if axios middleware successfully get a response from the api', () => {

    ...

});

In summary, if your action contains a request property, axios middleware will call the api in that url. And if the call is successful, it will create another action with type suffixed with “_SUCCESS”. Therefore, we verify the call is successful by checking that suffixed action type was ever recorded in the store.


const expectedAction = { type: YOUR_ACTION_TYPE + '_SUCCESS'};
return store.dispatch(YOUR_ACTION_CREATOR()).then(() => {
    expect(
        store.getActions().find(a => a.type === expectedAction.type)
    ).toBeDefined();
});

More in depth: when the store dispatch your action (aka the store takes in your action and tells every reducer that such an action has occurred), axios middleware call the api and eventually returns a Promise (more on Promise here).


store.dispatch(YOUR_ACTION_CREATOR()) // This is a Promise Object

If we use the then function on Promise, we ensure the testing logic happens after the original action and the succeeded (or failed) action have taken place.


.then(() => {
    ...
}

Then we ask the mock store all the action types that it ever recorded, and we check if the success suffix was one of them.


expect(
    store.getActions().find(a => a.type === expectedAction.type)
).toBeDefined();

That’s it! Putting the pieces and understanding how each part works do take some time, but the actual code is short. The beauty of React!

Leave a Reply

Your email address will not be published. Required fields are marked *