Writing and Testing async redux action creators with Jest

Published Jan 14, 2018
Writing and Testing async redux action creators with Jest

Redux action creators

Action Type
This is action type. Actions are plain JavaScript objects. Actions must have a type property that indicates the type of action being performed. Types should typically be defined as string constants. Once your app is large enough, you may want to move them into a separate module just the way i did here. Then we will be importing the action type to our action creator

export const SET_CURRENT_USER = 'SET_CURRENT_USER';

Helper function
This function helps set the token to the header, so it can be use whenever an end point that requires token is hit. We will be importing this function to our action creator.

import axios from 'axios';

export const setAuthorizationToken = (token) => {
  if (token) {
    axios.defaults.headers.common.Authorization = `Bearer ${token}`;
  } else {
    delete axios.defaults.headers.common.Authorization;
  }
};

Action Creator
Action creators are exactly that—functions that create actions. It's easy to conflate the terms “action” and “action creator,” so do your best to use the proper term.

Install axios and jsonwebtoken as dependencies

In Redux, action creators simply return an action:

import axios from 'axios';
import jsonwebtoken from 'jsonwebtoken';
import { SET_CURRENT_USER } from './types';
import { setAuthorizationToken } from '../helper';

/**
 * @description Request to the API to register a user
 *
 * @param  {object} userDetails the user deatils to be saved
 *
 * @return {object} dispatch object
 *
 */
export const signUpAction = (userDetails) => (dispatch) =>
  axios.post('/api/v1/users/signup', userDetails)
    .then((res) => {
      const token = res.data.data.token;
      localStorage.setItem('token', token);
      setAuthorizationToken(token);
      dispatch({
        type: SET_CURRENT_USER,
        user: jsonwebtoken.decode(token)
      });
    })
    .catch(error => Promise.reject(error.response.data.message));

Testing

Here is my test for the above action. All dependencies used for the test should be installed as dev-dependencies. Here we are importing our action, mocking the API call with moxios (which works well with axios) then returning mock data as API response and checking if our action payload is what we expect to see in the end. mockData is just a copy of API response as JSON, signupData is just a copy of the user input when trying to signup into the application and we also mockLocalStorage because we are saving the token to the local storage when user successfully signup.

Mock Data

export default const mockData = {
  authResponse: {
    status: true,
    message: 'You have successfully signed up',
    data: {
      token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1MTM5MzkzMDIsImN1cnJlbnRVc2VyIjp7InVzZXJJZCI6MSwidXNlcm5hbWUiOiJydWtraWV5In0sImlhdCI6MTUxMzg1MjkwMn0.K2P7BAKDkMbk9avQznEE4u8PRtrx3P0mlSzLvFAdH2E'
    }
  },
   signupData: {
    username: 'ruqoyah',
    fullName: 'Rukayat Odukoya',
    email: 'rukayat@gmail.com',
    password: 'oriyomi123'
  }
}

Mock Local Storage

let localStorage = {};

export default {
  setItem(key, value) {
    return Object.assign(localStorage, { [key]: value });
  }
};

import expect from 'expect';
import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import moxios from 'moxios';
import jsonwebtoken from 'jsonwebtoken';
import mockData from '../_mocks_/mockData';
import mockLocalStorage from '../_mocks_/mockLocalStorage';
import { signUpAction } from '../../actions/authActions';
import { SET_CURRENT_USER } from '../../actions/types';

const middlewares = [thunk];

const mockStore = configureMockStore(middlewares);

window.localStorage = mockLocalStorage;

describe('Auth actions', () => {
  beforeEach(() => moxios.install());
  afterEach(() => moxios.uninstall());

  it('creates SET_CURRENT_USER when signup action is successful', async (done) => {
    const { authResponse, signupData } = mockData;
    moxios.stubRequest('/api/v1/users/signup', {
      status: 201,
      response: authResponse
    });
    const expectedActions = [{ type: SET_CURRENT_USER,
      user: jsonwebtoken.decode(authResponse.data.token) }];
    const store = mockStore({});
    await store.dispatch(signUpAction(signupData))
      .then(() => {
        expect(store.getActions()).toEqual(expectedActions);
      });
    done();
  });
 });
 

Now that we know how to write and test redux actions, we need to write and test reducers, which I’ll cover in the next article. Stay tuned.

Discover and read more posts from Rukayat odukoya
get started