How to mock API calls in React Native
Data Science Engineer
23rd Oct 2019
Testing networking logic in React Native apps can be hard. You don't want to use the production API to run tests, so you need to mock network calls. Mocking also lets you test both the happy case where API works as expected as well as the case where the API fails.
There are different ways to mock network calls. You could use dependency injection to inject "fetching service" into the components. In tests, you would replace the real service with a mock. Or you could use Context to wrap components in a "fetching service" context. Both of these solutions can work, but there should be a simpler way.
In this post, we are going to build a basic React Native application tested in end-to-end fashion. We use Unmock to serve mock data to the app. Unmock is an HTTP testing library using node-mitm behind the scenes to intercept HTTP traffic. At interception, it generates random data mocking the API.
We'll run our tests in Node.js with Jest. We use React Native Testing Library to render the component and trigger React hooks. You can find the repository for this project here. Repository also includes instructions for running the app.
The sample application shows a random cat fact fetched from the Cat Facts API. User can refresh the fact by pressing the button. The app in all its glory looks like this, running here in Android virtual device:
Code for the app contains a single component defined in App.tsx. At high-level, we define the
App component like this:
useState from React hooks for managing the state of
loading. These variables contain the cat fact displayed to the user, possible fetch error, and the loading state.
refreshFact function refreshes the cat fact shown to the user:
This function sets the component state and uses
fetchFact function for the network call. The
fetchFact function uses Fetch API provided by React Native:
We parse the body by first parsing a JSON and extract the cat fact from the
text property as documented here.
Application component renders content based on the values of
If the state of
true, we show the text "Loading...". If the state of
err contains an error, user will see an apology. Otherwise, app shows the cat fact.
Note that we also give the components testID properties to simplify testing.
beforeAll block, we switch on Unmock with
unmock.on(). Then we add rules for intercepting all outgoing traffic for the Cat Facts API URL:
unmock.nock call, we also a give the name
catFactApi for the created fake service. Later in tests, we use the
catFactApi name to change the behaviour of the service.
In the behaviour for status code 200, we specify that the API should return a JSON body with
text property. The syntax
u.string('lorem.sentence') means that the value should be a fake sentence. See faker.js for other kinds of fake values you can use. Notice how we don't need to hardcode "foo" or "bar" in our tests!
Before each test, we reset the state of
unmock so that the tests remain decoupled:
The first test ensures that when the API returns a cat fact, the app contains the correct element:
Here we first set the API to always return 200, simulating success. We then use
library to render the component and run all hooks. We use
waitForElement to wait for the element with
testID="fact" to show up.
Second test for success ensures that when user clicks the button, the app fetches a new fact from the API. We simulate button press with the
Here we again use
waitForElement like above. This time we wait for an element containing the same text as the random fact returned from the API. Because the API returns a random sentence, we need to find its value. Unmock services keep track of mocked calls in the
spy property. This property is a SinonJS spy. The spy exposes its second call via the
secondCall property. The return value of that call is in
returnValue. See the chapter on expectations in Unmock documentation for more information.
Test for failure proceeds as the test for success. we change the API to return status code 500, render the app, and wait for the element with
testID="error" to show up.
That's it! Using Unmock, Jest and React Native Testing Library, we wrote comprehensive integration tests for our component. The tests made sure that the app triggers data fetching via React hooks. We also ensured that the app displays the returned cat fact without hardcoding "foo" or "bar". We also tested the case when the API call fails. We did not need to inject extra dependencies into our component or use contexts to mock the API.
unmock currently only supports Node.js environment. If you would like to see Unmock populate your React Native app with fake data, create an issue in unmock-js repository.
Thanks a lot for reading, as always we appreciate any feedback and comments!