javascriptreduxjestjsaxiosmoxios

How to mock an axios class


I am writing tests for my asynchronous actions. I have abstracted away my axios calls into a separate class. If I want to test my asynchronous redux action, how do I write a mock for api.js so that sampleAction.test.js will pass? Thanks!

api.js:

import axios from 'axios';

let apiUrl = '/api/';
if (process.env.NODE_ENV === 'test') {
  apiUrl = 'http://localhost:8080/api/';
}

export default class Api {
  static async get(url) {
    const response = await axios.get(`${apiUrl}${url}`, {withCredentials: true});
    return response;
  }
}

sampleAction.js: import Api from './api';

export const fetchData = () => async (dispatch) => {
  try {
    const response = await Api.get('foo');
    dispatch({
      type: 'RECEIVE_DATA',
      data: response.data.data,
    });
  } catch (error) {
    handleError(error);
  }
};

sampleAction.test.js:

import store from './store';

test('testing RECEIVE_DATA async action', () => {
  const expectedActions = [
    { type: 'RECEIVE_DATA', data: 'payload' },
  ];
  return store.dispatch(actions.fetchData()).then(() => {
    expect(store.getActions()).toEqual(expectedActions);
  });
});

Solution

  • You can mock Api.get like this:

    import { fetchData } from './sampleAction';
    import Api from './api';
    
    let getMock;
    beforeEach(() => {
      getMock = jest.spyOn(Api, 'get');
      getMock.mockResolvedValue({ data: { data: 'mock data' } });
    });
    afterEach(() => {
      getMock.mockRestore();
    });
    
    test('testing RECEIVE_DATA async action', async () => {
      const dispatch = jest.fn();
      await fetchData()(dispatch);
      expect(getMock).toHaveBeenCalledWith('foo');  // Success!
      expect(dispatch).toHaveBeenCalledWith({
        type: 'RECEIVE_DATA',
        data: 'mock data'
      });  // Success!
    });
    

    ...or you can mock api.js like this:

    import { fetchData } from './sampleAction';
    import Api from './api';
    
    jest.mock('./api', () => ({
      get: jest.fn(() => Promise.resolve({ data: { data: 'mock data' } }))
    }));
    
    test('testing RECEIVE_DATA async action', async () => {
      const dispatch = jest.fn();
      await fetchData()(dispatch);
      expect(Api.get).toHaveBeenCalledWith('foo');  // Success!
      expect(dispatch).toHaveBeenCalledWith({
        type: 'RECEIVE_DATA',
        data: 'mock data'
      });  // Success!
    });
    

    ...or you can auto-mock api.js and fill in the return value for Api.get:

    import { fetchData } from './sampleAction';
    import Api from './api';
    
    jest.mock('./api');  // <= auto-mock
    Api.get.mockResolvedValue({ data: { data: 'mock data' } });
    
    test('testing RECEIVE_DATA async action', async () => {
      const dispatch = jest.fn();
      await fetchData()(dispatch);
      expect(Api.get).toHaveBeenCalledWith('foo');  // Success!
      expect(dispatch).toHaveBeenCalledWith({
        type: 'RECEIVE_DATA',
        data: 'mock data'
      });  // Success!
    });
    

    ...or you can create a manual mock at ./__mocks__/api.js:

    export default {
      get: jest.fn(() => Promise.resolve({ data: { data: 'mock data' } }))
    }
    

    ...and activate it in your test like this:

    import { fetchData } from './sampleAction';
    import Api from './api';
    
    jest.mock('./api');  // <= activate manual mock
    
    test('testing RECEIVE_DATA async action', async () => {
      const dispatch = jest.fn();
      await fetchData()(dispatch);
      expect(Api.get).toHaveBeenCalledWith('foo');  // Success!
      expect(dispatch).toHaveBeenCalledWith({
        type: 'RECEIVE_DATA',
        data: 'mock data'
      });  // Success!
    });