Test ajax calls in react component lifecycle cover

While developing React application you will most likely need to fetch some data from API. If you're using class components a common place to do this is componentDidMount lifecycle method. But then you might be wondering how to properly write unit tests for it? In this blog post I will show basic examples how to write tests for HTTP calls inside class methods.

Our case

To prepare examples I have used fresh create-react-app project. It provides base setup for webpack, babel and jest test framework. Finally I have also included enzyme to render components in tests and axios as HTTP client.

So first, let's create a component for which we will try to write unit tests. The component will be named ArticleList and it will use the local state to hold articles as an array of objects. The articles are fetched from some external API and it's done by calling axios.get inside componendDidMount. Then it renders articles as a list. That's how it looks:

import React from 'react';
import axios from 'axios';

export default class ArticleList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      articles: []
    }
  }

  componentDidMount() {
    return axios.get('GET_ARTICLES_URL').then(response => {
      this.setState({
        articles: response.data
      });
    });
  }

  render() {
    return (
      <ul>
        {this.state.articles.map(a => <li><a href={a.url}>{a.title}</a></li>)}
      </ul>
    )
  }
}

Next, we move to writing a unit test for ArticleList component. We want to make sure that each time component is rendered, it calls API at given endpoint. We will use shallow method to render component and then call componentDidMount manually.

import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

it('fetch articles on #componentDidMount', () => {
  const app = shallow(<App />);
  app.instance().componentDidMount();
});


Our test should cover two cases here. First, whether axios.get gets called and second, if the component state is updated correctly after the server response.

Mock modules

In order to expect whether some library methods are called we can use `jest.mock` function. It allows us to override some modules for test suite. We can use it like this:

jest.mock('axios', moduleFn);

The first argument is a module name and the second is a function which should return module mock. In our case it will take such a form:

() => {
  const exampleArticles = [
    { title: 'test article', url: 'test url' }
  ];
  
  return {
    get: jest.fn(() => Promise.resolve(exampleArticles)),
  };
}

We define our module to have single function get which immediately resolves Promise with some example article items. We also wrapped it inside jest.fn() to have spy methods on module function. To check if the function was called we will use toHaveBeenCalled and toHaveBeenCalledWith. Except giving us a possibility to return any value from module method that is suitable for our test case, writing mock for module gives us one more important feature. Unit tests should be run in isolation thus we shouldn't make any external calls to the server. Mocking axios module makes unit tests independent of the network.

Here's our updated test with expectations: 

import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

jest.mock('axios', () => {
  const exampleArticles = [
    { title: 'test article', url: 'test url' }
  ];
  
  return {
    get: jest.fn(() => Promise.resolve(exampleArticles)),
  };
});

const axios = require('axios');

it('fetch articles on #componentDidMount', () => {
  const app = shallow(<App />);
  app.instance().componentDidMount();
  expect(axios.get).toHaveBeenCalled();
  expect(axios.get).toHaveBeenCalledWith('articles_url');
});

Expect with asynchronus code

Now comes the second part of our test, check how the component state gets updated. To do this we need to change our test to the async one. We need to wait for axios.get to resolve to inspect state and call done() to complete the test.

it('fetch articles on #componentDidMount', async (done) => {
  const app = shallow(<App />);
  app
    .instance()
    .componentDidMount()
    .then(() => {
      expect(axios.get).toHaveBeenCalled();
      expect(axios.get).toHaveBeenCalledWith('articles_url');
      expect(app.state()).toHaveProperty('articles', [
        { title: 'test article', url: 'test url' }
      ]);
      done();
    });
});

We've added then to promise returned from componentDidMount function. This will ensure we're running expect after promise resolves with example articles. Then we're using state() on the component instance to get the current state. At this stage, it should be updated with new articles.

Finally are complete unit test will look like this:

import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

jest.mock('axios', () => {
  const exampleArticles = [
    { title: 'test article', url: 'test url' }
  ];
  
  return {
    get: jest.fn(() => Promise.resolve(exampleArticles)),
  };
});

const axios = require('axios');

it('fetch articles on #componentDidMount', () => {
  const app = shallow(<App />);
  app
    .instance()
    .componentDidMount()
    .then(() => {
      expect(axios.get).toHaveBeenCalled();
      expect(axios.get).toHaveBeenCalledWith('articles_url');
      expect(app.state()).toHaveProperty('articles', [
        { title: 'test article', url: 'test url' }
      ]);
      done();
    });
});

Conclusion 

Unit tests are very important for long-term development and application stability. With component-based programming React offers, testing your code became a lot easier. However, there are still some cases where writing tests properly may be tricky. I hope this post will help you with one of them which is testing lifecycle and HTTP calls.

Post tags:

Join our awesome team
Check offers

Work
with us

Tell us about your idea
and we will find a way
to make it happen.

Get estimate