This site runs best with JavaScript enabled.

How to test API calls

Daniel Afonso

May 17, 2021


Hey everyone, recently I've seen a couple of questions regarding testing in the JavaScript ecosystem. Some of the questions are:

  • How to test API calls?
  • How to test axios?
  • How test API calls fired from a useEffect hook?
  • How to test API calls from React Query?

During this blog post, I'll give you all the ways that I know off about how to test API calls. For learning purposes, I'll be using axios for my examples.

Scenario

Let's imagine that we have a component that will fire an API request either being through the useEffect hook or a useQuery custom hook. When we received the data we destructure the name from it and render it. Here is a possible implementation

function Example() {
const { isLoading, error, data, isFetching } = useQuery('repoData', () =>
axios
.get('https://api.github.com/repos/tannerlinsley/react-query')
.then(res => res?.data),
)
if (isLoading) return 'Loading...'
if (error) return 'An error has occurred: ' + error.message
return (
<div>
<h1>{data.name}</h1>
</div>
)
}

Now a typical way to test this would be to render our component and wait for the name returned from the API call to be rendered on the screen. Using React Testing Library the expected test should be the following:

test('Example', async () => {
render(<App />)
const text = await screen.findByText(/value from the api/i)
expect(text).toBeInTheDocument()
})

Now the question is, how do we make sure that the requests in our tests return the expected data?

Using axios-mock-adapter

axios-mock-adapter is the first solution. It allows you to mock axios while giving you a more readable syntax to explicitly declare what you want to do. In the following example, we get the axios reference and create a new MockAdapter instance. Using this instance, we then use theonGet` method to make sure that when we fire a GET request to our expected URL we return the data we want it to.

import axios from "axios";
import MockAdapter from "axios-mock-adapter";
const mock = new MockAdapter(axios);
mock
.onGet("https://api.github.com/repos/tannerlinsley/react-query")
.reply(200, {
name: "value from the api",

Mocking axios manually

You can use Jest to mock axios yourself and avoid installing external libraries. In the following approach, we use jest.mock to mock axios, and then inside of our test we mock the get method implementation to return our expected data for the expected URL.

import axios from 'axios'
jest.mock('axios')
test('Example', async () => {
axios.get.mockImplementation(url => {
if (url === 'https://api.github.com/repos/tannerlinsley/react-query') {
return Promise.resolve({ data: { name: 'value from the api' } })
}
})
render(<App />)
const text = await screen.findByText(/value from the api/i)
expect(text).toBeInTheDocument()
})

If you don't like the approach about you can create an axios.js file inside of the __mocks__ directory and add the following snippet there.

export default {
get: jest.fn(() => Promise.resolve({ data: { name: "value from the api" } })),
};

When you run your test then Jest will automatically pick up that file and mock axios for you.

Using MSW

MSW changed the way I test everything. Even though we have all the other approaches I mentioned above maintaining mocks can escalate fast and after a while, you will end up with a bunch of them. To save time and centralize everything I use MSW.

For this scenario, all we need to do is create a handler for our request that returns the data we expect it to. Then we either add the handler to our handler list or call server.use in our test to override our handlers.

server.use(
rest.get('https://api.github.com/repos/tannerlinsley/react-query', (req, res, ctx) =>
res(ctx.status(200), ctx.json({ name: 'value from the api' })),
),
)

If you want to learn more about MSW, you can read my blog post about it.

Wrapping up

As you can see, when testing API calls, there are a bunch of options you can decide to use. While right now I prefer to use MSW, in the past I've resorted to all the other options.

I hope you found this blog post useful. Stay tuned for more and see you next time!

Share article

Daniel Afonso © 2021