Loading... Search articles

Search for articles

Sorry, but we couldn't find any matches...

But perhaps we can interest you in one of our more popular articles?
In this article, we’ll learn how to test API calls in React Native applications.

How to test API calls in React Native applications

Apr 7, 2021

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.


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).

Latest articles

Show more posts