In this article, we’ll learn how to test API calls in React Native applications.
This article is written by Sneh Pandya
React Native has emerged as one of the most popular cross-platform mobile application frameworks. Along with its growing adoption, the importance of the stability of apps built using React Native is also growing. React Native provides testing methods, but none of them thoroughly cover the actual logic written in the apps. Therefore, React Native apps benefit more from functional testing.
Let’s get started!
Testing React Native apps
A React Native application can be tested with a variety of tools, such as:
WebDriver: an open-source testing tool for Node.js apps that is also widely used to test React Native applications.
Jest: one of the most popular testing libraries out there, which includes unit testing capabilities straight out of the box.
Mocha: a popular library for testing React and React Native applications. It has become a testing tool of choice for developers because of its easy setup and usability.
Jasmine: Jasmine is a behavior-driven development framework for testing JavaScript code, also used for testing React and React Native applications.
Enzyme: Enzyme is a JavaScript testing framework focused towards React Native applications. You can also manipulate, traverse, and in some ways simulate runtime given the output.
We will proceed with Jest, Enzyme and Axios mock adapter for this particular article to test API calls in our React Native application. Jest and Enzyme act as the backbone of the testing stack. We will use enzyme
to do our set up as well as simulate runtime behaviour, and axios-mock-adapter
to mock our API calls in order to test them.
Setting up the React Native application
Let’s get started by setting up our project. React Native creates directories for both Android and iOS that are labeled android
and ios
, respectively. To create a new application, run the commands as follows:
npm install -g expo-cli // To add Expo CLI on your machine
npx react-native init ReactNativeAPITesting // To set up a new React Native app
cd ReactNativeAPITesting
The dependencies required for the project setup are shown below. Copy them into your package.json
file:
{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"test": "jest"
},
"jest": {
"preset": "jest-expo",
"collectCoverage": true,
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)"
],
"setupFilesAfterEnv": [
"<rootDir>src/setupTests.js"
],
"collectCoverageFrom": [
"**/*.{js,jsx}",
"!**/coverage/**",
"!**/node_modules/**",
"!**/babel.config.js",
"!**/jest.setup.js"
]
},
"dependencies": {
"@react-native-community/hooks": "^2.5.1",
"@react-native-community/masked-view": "0.1.6",
"@react-navigation/bottom-tabs": "^5.7.3",
"@react-navigation/native": "5",
"@react-navigation/stack": "^5.7.1",
"apisauce": "^1.1.2",
"babel-jest": "^26.3.0",
"babel-preset-react-native": "^4.0.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.4",
"expo": "~37.0.3",
"expo-cli": "^3.27.10",
"expo-constants": "^9.0.0",
"expo-image-picker": "~8.1.0",
"expo-location": "~8.1.0",
"expo-permissions": "~8.1.0",
"faker": "^5.1.0",
"formik": "^2.1.4",
"global": "^4.4.0",
"jest": "^26.4.2",
"jest-react-native": "^18.0.0",
"lottie-react-native": "~2.6.1",
"react": "~16.9.0",
"react-dom": "~16.9.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz",
"react-native-gesture-handler": "~1.6.0",
"react-native-reanimated": "~1.7.0",
"react-native-safe-area-context": "0.7.3",
"react-native-screens": "~2.2.0",
"react-native-web": "~0.11.7",
"react-test-renderer": "^16.13.1",
"yup": "^0.29.1"
},
"devDependencies": {
"@babel/core": "^7.8.6",
"babel-preset-expo": "~8.1.0",
"jest-expo": "^38.0.2"
},
"private": true
}
Next, install the dependencies using the command below:
npm install
// OR
yarn install
For brevity, we will create a simple application in this article.
import React from 'react';
import { StyleSheet, Text, TouchableOpacity } from 'react-native';
function AppButton({ onPress }) {
return (
<TouchableOpacity
style={[styles.button,
{ backgroundColor: colors[color] }]}
onPress={onPress} >
<Text style={styles.text}>Register</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: red;
borderRadius: 25,
justifyContent: 'center',
alignItems: 'center',
},
text: {
color: #fff
}
})
export default AppButton;
Adding Jest to the React Native application
In this section, we’ll test for user interaction and the UI of the app components by testing snapshots using Jest. To achieve this, we need to install Jest and its dependencies:
yarn add jest-expo --dev
Then, update your package.json
file with the test script:
"scripts": {
"test" "jest"
},
"jest": {
"preset": "jest-expo"
}
Next, to perform comprehensive tests, execute the command below:
npm i react-test-renderer --save-dev
We need to add a transformIgnorePattern configuration that prevents tests from running in Jest whenever a source file matches a test:
"jest": {
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)"
]
}
Let’s write a basic test first. Create a file named App.test.js
, and add the details as shown below:
import React from "react";
import renderer from "react-test-renderer";
import App from "./App.js"
describe("<App />", () => {
it('has 1 child', () => {
const tree = renderer.create(<App />).toJSON();
expect(tree.children.length).toBe(1);
});
});
Now run npm test
or yarn test
in your terminal. If App.js
has a single child element, the test will pass.
Calling APIs in React Native
To call APIs and test them, we are going to make use of mock functions. Mock functions are copies of objects or a function without the real workings of that function. Rather, it imitates that particular function. Mocks mainly reduce the need for dependencies.
React Native includes fetch
in the global object. To avoid making real API calls in our unit test, we mock them. A way to mock is shown below:
global.fetch = jest.fn();
// mocking an API success response
fetch.mockResponseIsSuccess = (body) => {
fetch.mockImplementationForOnce (
() => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))})
);
};
// mocking an API failure response
fetch.mockResponseIsFailure = (error) => {
fetch.mockImplementationForOnce(
() => Promise.reject(error)
);
};
The function tries to fetch an API once. Having done this, it returns a promise, and when it is resolved, it returns the body in JSON. It’s similar to the mock response for a failed fetch transaction – it returns an error. Below is a component of our app:
import React from 'react';
const Product = () => {
const product = {
name: 'Pizza',
quantity: 5,
price: '$50'
}
return (
<>
<h1>Name: {product.name}</h1>
<h1>Quantity: {product.quantity}</h1>
<h1>Price: {product.price}</h1>
</>
);
}
export default Product;
Now we will try to test all our components. Directly accessing our database is not a feasible solution. As a solution, we need to mock a component of the product by using Jest
to describe the objects in the component as below:
import { mount } from 'enzyme';
describe("<App />", () => {
it("accepts products props", () => {
const wrapper = mount(<Customer product={product} />);
expect(wrapper.props().product).toEqual(product);
});
it("contains products quantity", () => {
expect(value).toBe(3);
});
});
Enzyme is used in the above code snippet to perform the full rendering, which actually mounts the component in the DOM using mount
keyword. It means that tests can affect each other if they are all using the same DOM. While writing tests, if necessary, you should also use unmount
or something similar to cleanup the DOM.
Mocking APIs in React Native
Now let’s mock an external API call. To test an external call to an API, you need to mock requests and also manage the responses. First, we need to install dependencies by running the command below:
npm install axios
// OR
yarn add axios-mock-adapter
Next, let’s create the mocks. If the request is successful, a response with a status code of 200 will be received.
import MockAdapter from 'axios-mock-adapter';
import Faker from 'faker'
import ApiClient from '../constants/api-client';
import userDetails from 'jest/mockResponseObjects/user-objects';
let mockApi = new MockAdapter(ApiClient.getAxiosInstance());
let validAuthentication = {
name: Faker.internet.email(),
password: Faker.internet.password()
mockApi.onPost('requests').reply(config) => {
if (config.data === validAuthentication) {
return [200, userDetails];
}
return [400, 'Incorrect username and password'];
});
The mock is ready, so let’s write a test for an external API request:
it('successful sign in with correct credentials', async () => {
await store.dispatch(authenticateUser('ikechifortune@gmail.com', 'password'));
expect(getActions()).toMatchSnapshot();
});
it('unsuccessful sign in with wrong credentials', async () => {
await store.dispatch(authenticateUser('ikechifortune@gmail.com', 'wrong credential'))
.catch((error) => {
expect(errorObject).toMatchSnapshot();
});
Here, we’re testing a successful login with the correct credentials, using the native JavaScript async await
. Meanwhile, the authenticateUser
function from Jest authenticates the request and makes sure it matches our earlier snapshots. Similarly, we test for an unsuccessful login in case incorrect credentials are used, such as an incorrect email address or password.
Run yarn test
or npm test
in your terminal, and wait till the tests finish.
Conclusion
We have covered the basics of API testing for React Native applications using Jest. With Jest, testing React Native applications has never been easier, especially with snapshots, which ensure that the UI remains consistent regardless of the global styles. The test-automation frameworks covered here are widely accepted and used for React Native apps.
What are you using to test React Native apps? 🤔 Add your comment here.
Useful links and resources
Here’s an article on React Native Detox tests on Codemagic.
Here’s an article on testing React Native apps with Jest and Codemagic.
Here’s an article on why Android developers should pay attention to React Native in 2021.
Here’s an article on choosing the right database for your React Native app.
Here’s an article on how to improve performance of a React Native app.
Here’s a practical guide on Continuous Integration and Delivery for React Native apps.
For discussions, learning and support, join the Codemagic Slack Community.
Sneh is a Senior Product Manager based in Baroda. He is a community organizer at Google Developers Group and co-host of NinjaTalks podcast. His passion for building meaningful products inspires him to write blogs, speak at conferences and mentor different talents. You can reach out to him over Twitter (@SnehPandya18) or via email (sneh.pandya1@gmail.com).