Commit ID:e742a7462a0aa6a543c39cf4b5cea8ad2fecff00

Ximedes exists to allow a group of smart, friendly and ambitious professionals to work together on relevant and challenging software projects, to the delight of our clients and ourselves. Contact us

Test Redux action creators with Jest

Hielke de Vries
February 03, 2017

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.

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 () => {
    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:

  • LoginType.AUTHENTICATION_PENDING
  • 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(). The function 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:

  • { type: LoginType.AUTHENTICATION_PENDING }
  • { 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.