Asynchronous API testing in React

Testing asynchronous code in React can be a bit of a pain, particularly when that code contains API calls. There are any number of libraries that help with this situation, but for this post, I’ll be relying on Jest’s own mocking capabilities, as well as Enzyme for mounting.

Mocking fetch

Consider the following React Component:

What do we really care about when it comes to testing our componentDidMount method? In my view, we should be testing that the fetch call is made once the lifecycle method fires, that the groceries are set in the component’s state, or that the errorStatus is set in the event of an error. I don’t like having the naked fetch call here in the lifecycle method, but for now we’ll work around that.

If we run these tests right now, we’re not going to be pleased with the result. Jest isn’t going to actually make any fetch calls, and will instead spit out something along the lines of:

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Network request failed

The thing to remember is that we don’t actually care what the API is returning, we can’t really control that. We can only control what we expect it to return. With that in mind, Jest makes it easy to mock our fetch calls, and stub them with the expected data, using mockImplementation. That way we can test that our application is digesting the data the way we want it to.

Now, our tests pass! Together, these tests ensure that we’re making a fetch call, and that both the happy and sad paths are tested. I’m not totally satisfied with how these tests look however; mocking the fetch calls in each method makes the tests harder to read. I could instead mock my fetch calls all at once, at the beginning of the file, using mockImplementationOnce:

In my opinion, the tests are more readable this way, as all the mocking happens in one place, at beginning of the file. The tradeoff is the tests now must run in order, or the test won’t be using the stubbed data that it’s expected.

Refactoring out fetch

I mentioned when we started writing our tests for App.js that I don’t like having the naked fetch calls in the componentDidMount lifecycle method. The reason for this is twofold. If our app component grows even a little further, and we have to make another fetch call somewhere, we’ll have to update our mocking, making sure not to break any ordering. This could get cumbersome, as well as being hard to read. Furthermore, if we need to call the same endpoint again somewhere else in our code, we’ll have to repeat ourselves, rewriting the fetch call.

I prefer to keep my calls to external services in their own files. This makes it easier to test both the behavior or those fetch's, as well as how my components will digest the data.

Here, I’ve pulled the fetching of groceries into its own asynchronous method. Testing this in isolation is really easy, using a similar strategy as before. Now what we care about is that fetchGroceries throws an error when it should, and returns data otherwise.

In the test, we’ll mock the implementation of the fetch function, and assert that we get what we expect back from the method. Jest makes it easy (as of version 20+) to handle the output of an asynchronous function using resolves/rejects.

Pulling it together

Now that I have a reusable and tested method for fetching groceries, let’s use it in our React component.

Our tests for App.js still pass, since the fetchGroceries method is calling out to fetch, and we’ve mocked the implementation of it. However, it’d be better to mock out the implementation of fetchGroceries now, so that we’re truly testing componentDidMount in isolation. Jest makes it really easy to mock whole modules with mock, which will also allow us to separate our tests and our mocks.

Line 5 above will look for a file named apiCalls.js inside the __mocks__ directory, and use that to override our methods with the same signature. We can then use the same mockImplementationOnce strategy that we employed before:

Now on the first call of fetchGroceries we will return a data object, and on the second call we’ll throw an error. If that’s at all confusing, realize that Jest is looking for the mock in relation to where the real file lives, in the __mocks__ directory. For this project, the directory structure would look something like this:

Final thoughts

As a rule of thumb, code is easier to test when it is doing less. By separating our API calls from component code, it’s easier to test the expected behavior of both pieces. By using the mocking and asynchronous expectations that are available in Jest, it’s easy to mimic the behavior of an API, and ensure that your application responds as you expect it should.

Like what you read? Give Will Mitchell a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.