Unit testing the Axios call using enzyme in a react component

Axios library is used to fetch the data. Usually in react components, we use the Axios library to do the data fetching part. Unit testing the data fetch part is very important so that both the success and the error condition are handled and tested.

In this tutorial, we are going to make a api call using Axios library in our component, and then test the success and error condition of the api call.

Here is a sample React component that uses the Axios library in the lifecycle. We are going to fetch the response, get the name from the response, and set the state variable in the success condition.

In case of error, we are going to set the error state as true, which will render the generic error text.

import React, { Component } from 'react';
import axios from 'axios';

class MockComponent extends Component{
  state = {
    name: '',
    error: false
  }
  componentDidMount(){
   this.getData();
  }
  getData = () => {
    axios({
      url: '/',
      method: 'POST'
    }).then((data)=>{
      const { name } = data;
      this.setState({
        name
      })
    }).catch(()=>{
      this.setState({
        error: true
      })
    })
  }
  render(){
    const { name, error } = this.state;
    if(error){
      return (
        <div>
          Error fetching data
        </div>
      )
    }
    return (
      <div>
        This is a mock component {name}
      </div>
    )
  }
}

export default MockComponent;

Installing the react testing library

Now we are going to use the react testing library to test the above component. If you do not have the testing library installed, then install the dev dependency by running the command below

npm install --save-dev @testing-library/react

Now we have the react testing library installed. Let us start writing the unit test case for the above component.

Mocking the Axios library

We have to mock the axios library so that we can control the success and error condition and it can be easily reproduced in the test case. The axios library is a es6 module, so we are going to mock it as follows.

jest.mock('axios', () => {
  return {
    __esModule: true,
    default: jest.fn()
  }
});

In the above code, we have mocked the axios library using jest.mock. jest.mock accepts a callback function, where we can define the properties of the axios library. Because it is an es6 module, we have returned __esModule as true. We have to also implement the default export of the axios library as we are using the default import in our MockComponent.

We have assigned a mock function to the default export of Axios, because we are going to spy on it and return the resolved value or rejected value in the test case as follows

Returing the resolved value

const axios = require('axios');
jest.spyOn(axios, 'default').mockResolvedValue({
  name: 'abc'
})

Returning the rejected value

const axios = require('axios');
jest.spyOn(axios, 'default').mockRejectedValue()

Writing the unit test case

We have now understood how to mock the Axios library and control it by spying on the Axios library and returning the resolved or rejected value as per our test case. So let us write the unit test case for success condition.

import React from 'react';
import { shallow } from 'enzyme';
import MockComponent from '../mock';
jest.mock('axios', () => {
  return {
    __esModule: true,
    default: jest.fn()
  }
});

describe('MockComponentEnzyme', ()=>{
  it('should get data', (done) => {
    const axios = require('axios');
    jest.spyOn(axios, 'default').mockResolvedValue({
      name: 'abc'
    })
    const wrapper = shallow(<MockComponent/>, {
      disableLifecycleMethods: true
    });
    wrapper.instance().getData();
    process.nextTick(()=>{
      expect(wrapper.state('error')).toBeFalsy();
      expect(wrapper.state().name).toEqual('abc');
      done();
    })
  })
})

One point to note in the above unit test case is that we have inserted the mock before importing the MockComponent.

First, we mock the Axios component in the imports section. Then in the test case, we require the mock Axios library to get the instance, so that we can add a spy on it and mock its implementation as well.

We are going to use the enzyme to render the component. We will disable the lifecycle method so that componentDidMount method is not called. We are going to call the getData method ourselves after the component setup is done.

const wrapper = shallow(<MockComponent/>, {
  disableLifecycleMethods: true
});
wrapper.instance().getData();

The axios call is going to be made in the componentDidMount lifecycle. So we are going to verify if the text of success condition has been rendered in the DOM.

We have to use process.nextTick because the setState is an async process.

Also note that we have used the done callback, so that the test case will wait unit the done callback is called. We call the done callback inside the process.nextTick function.

Writing the unit test case for the error condition

As discussed above, we will follow the similar pattern and write the unit test case for the error condition. But in the mock implementation, we have to send the rejected value, and not the resolved value so that the catch block of the axios call is used.

...
  it('should handle error data', (done) => {
    const axios = require('axios');
    jest.spyOn(axios, 'default').mockRejectedValue()
    const wrapper = shallow(<MockComponent/>, {
      disableLifecycleMethods: true
    });
    wrapper.instance().getData();
    process.nextTick(()=>{
      expect(wrapper.state('error')).toBeTruthy();  
      done();
    })
  })

Review of entire unit test case file

Lets see the entire unit test case with both success and error condition.

import React from 'react';
import { shallow } from 'enzyme';
import MockComponent from '../MocksEnzyme';
jest.mock('axios', () => {
  return {
    __esModule: true,
    default: jest.fn()
  }
});

describe('MockComponentEnzyme', ()=>{
  it('should get data', (done) => {
    const axios = require('axios');
    jest.spyOn(axios, 'default').mockResolvedValue({
      name: 'abc'
    })
    const wrapper = shallow(<MockComponent/>, {
      disableLifecycleMethods: true
    });
    wrapper.instance().getData();
    process.nextTick(()=>{
      expect(wrapper.state('error')).toBeFalsy();
      expect(wrapper.state().name).toEqual('abc');
      done();
    })
  })

  it('should handle error data', (done) => {
    const axios = require('axios');
    jest.spyOn(axios, 'default').mockRejectedValue()
    const wrapper = shallow(<MockComponent/>, {
      disableLifecycleMethods: true
    });
    wrapper.instance().getData();
    process.nextTick(()=>{
      expect(wrapper.state('error')).toBeTruthy();  
      done();
    })
  })
})