3 February 2017 Hielke de Vries

Test Redux action creators with Jest

Intro

At Ximedes, we’ve built many React apps and use Redux for almost all our front-end related projects. For most React projects the Mocha/Chai/Sinon test stack is used, because competitor Jest was just not as good. However, it really started to catch up lately in terms of quality. This blog shows how we test Redux-thunk action creators, using only Jest.

TLDR;

Here is the full example.

Let’s begin.

Say we have a user login screen that comes along with the following Redux action creators:

import axios from 'axios';
import { LoginType } from './ActionTypes';

export function loginButtonPressed(username, password) {
    return async (dispatch, getState) => {
        const url = 'https://endpoint.local';
        dispatch(authenticationPending());
        try {
            await axios.post(url,
                {
                    username,
                    password
                });
            dispatch(authenticationSuccess());
        } catch (error) {
            dispatch(authenticationFailed());
        }
    };
}

export function authenticationPending() {
    return {
        type: LoginType.AUTHENTICATION_PENDING
    };
}

export function authenticationSuccess() {
    return {
        type: LoginType.AUTHENTICATION_SUCCESS
    };
}
export function authenticationFailed() {
    return {
        type: LoginType.AUTHENTICATION_FAILED
    };
}

This is a typical set of action creators:

  • One asynchrous action creator (thunk) which deals with async HTTP requests.
  • Three synchronous action creators which deal with:
    • waiting on the HTTP response (authenticationPending),
    • when the HTTP response is OK (authenticationSuccess),
    • failure (authenticationFailed).

When the user clicks the login button, loginButtonPressed() is called and LoginType.AUTHENTICATION_PENDING is dispatched. When the authentication is successful, LoginType.AUTHENTICATION_SUCCESS is dispatched or else LoginType.AUTHENTICATION_FAILED when the authentication failed.

Async/Await

The async/await syntax is a just cleaner way of saying .then().catch(). When await loginButtonPressed('test_user', 'test_password')(dispatch, getState); is called, executing the next line of code is halted until a result has come back from axios.post(). If the Promise is resolved, execution continues. If it is rejected, the execution stops and an error is thrown and caught.

The code to test the success/happy flow of loginButtonPressed(), is below. Let’s go through it step-by-step:

import axios from 'axios';

import { loginButtonPressed } from '../../main/actions/userActions';
import { LoginType } from '../../main/actions/ActionTypes';

describe('userActions', () => {
    it('Login successful, creates AUTHENTICATION_PENDING and AUTHENTICATION_SUCCESS', async () => {
        // prepare
        const expected = [
            { type: LoginType.AUTHENTICATION_PENDING },
            { type: LoginType.AUTHENTICATION_SUCCESS }
        ];

        // mock the axios.post method, so it will just resolve the Promise.
        axios.post = jest.fn((url) => {
            return Promise.resolve();
        });
        // mock the dispatch and getState functions from Redux thunk.
        const dispatch = jest.fn(),
            getState = jest.fn(() => {url: 'https://endpoint.local'});

        // execute
        await loginButtonPressed('test_user', 'test_password')(dispatch, getState);

        // verify
        expect(dispatch.mock.calls[0][0]).toEqual(expected[0]);
        expect(dispatch.mock.calls[1][0]).toEqual(expected[1]);
});

First at const expected =, we expect two actions to be dispatched:

  1. LoginType.AUTHENTICATION_PENDING
  2. LoginType.AUTHENTICATION_SUCCESS

Now at axios.post = jest.fn((url), we make use of Jest’s included function mocking. We mock out the HTTP POST request with jest.fn(() => Promise.resolve();). This will simulate a successful HTTP response. Furthermore we mock both thunk functions, dispatch() and getState(). getState() should return a Redux state object with a URL needed for the axios POST request.

Next, we call loginButtonPressed(). As a Redux thunk is nothing more than a function that returns a function, we call the result of loginButtonPressed() immediately again with our mocked functions as arguments: dispatch and getState.

Lastly, assertion is done. Each Jest mocked function gets a mock property, in where all sorts of information about the function is stored. E.g. with which argument it is called. We assert dispatch() to have been called with:

  1. { type: LoginType.AUTHENTICATION_PENDING }
  2. { type: LoginType.AUTHENTICATION_SUCCESS }

That’s it

We have used only Jest to test action creators, instead of the Mocha, Chai and Sinon stack. This stack used to be one of the best to use, but since Jest was rewritten begin 2016, it has gotten much better.

Tagged: , , , ,