Apache Cordova, since its release in 2009, has created a paradigm shift for mobile application development. Before Cordova, only developers who knew platform dedicated languages for a particular type of mobile operating system could develop native mobile applications. There was Objective-C for developing iOS apps, or Java for Android apps and platforms like Blackberry. Windows Phone also had languages dedicated to building their mobile applications. Apache Cordova (Cordova for short) broke the monopoly on platform languages. Cordova gives any developer with a knowledge of HTML, CSS, and Javascript the power to build mobile applications with a single codebase and compile them for any mobile platform available.

Because of the Webview rendering engine in Cordova apps, some developers argued that these apps were not truly “native”. React Native settles that argument. React allows developers to create truly native apps by providing JavaScript components that map directly to the platform’s native UI building blocks.

In this tutorial, you will learn how to:

  • Write and run tests for your React Native applications
  • Automate how these tests run by building a continuous integration pipeline

Prerequisites

To follow this tutorial, a few things are required:

  1. Basic knowledge of Javascript
  2. Node.js (version >= 10.13) installed on your system
  3. A CircleCI account
  4. A GitHub account
  5. An environment that is set up for React (native development for Android)

Note: A React environment is conditionally optional. Without it, you will not be able to run the sample application in an emulator. You will still be able to run the tests described in the tutorial.

With all these installed and set up, it is time to begin the tutorial.

Creating a sample React Native application

To begin, create a new React Native application. Choose a location for the app, then run:

npx react-native init MyTestProject

If prompted, press Enter to continue. This command uses npx to invoke the react-native init command to scaffold the new app project inside a MyTestProject folder.

Use Android Studio to open the MyTestProject/android folder. Then, go to the Tools > AVD Manager. Run any of your Android Virtual Devices (AVD) by clicking the play button next to it. If you do not have an AVD, create one by clicking + Create Virtual Device from the Android Virtual Device Manager window.

AVD running - Android Studio

In this tutorial, you will be creating a simple React Native application that gives users a button to click that displays a message. Then you will write a test suite to test this behavior.

Go to the App.js file (in the root folder) and replace the code in it with:

import React from "react";
import { Button, Text, TextInput, View, StyleSheet } from "react-native";

const App = () => {
  const [message, setMessage] = React.useState();

  return (
    <View>
      <Button
        title="Say Hello"
        onPress={() => {
          setTimeout(() => {
            setMessage("Hello Tester");
          }, Math.floor(Math.random() * 200));
        }}
      />
      {message && (
        <Text style={styles.messageText} testID="printed-message">
          {message}
        </Text>
      )}
    </View>
  );
};

const styles = StyleSheet.create({
  messageText: {
    fontFamily: "Arial",
    fontSize: 38,
    textAlign: "center",
    marginTop: 10
  }
});

export default App;

This code creates the (very simple) UI I described earlier: a button labeled Say Hello displays the message Hello Tester when it is clicked. To simulate an asynchronous operation, we are using setTimeout to delay the display by a fraction of a second after the button is clicked. The React Native Text component displays the message just at the bottom of the button. The code adds styles to the message by using the Stylesheet component.

To run this project, open a terminal window, then, start up Metro. Metro is a Javascript bundler that enables reloads of your application in the emulator any time file changes occur. Start Metro:

npx react-native start

Note: If the app does not start automatically, open the application drawer in the emulator and manually start the MyTestProject app.

Leave Metro running in its own terminal window (make sure your emulator is still running). Open another terminal window to run the React Native application in the emulator:

npx react-native run-android

App running - Emulator

On the screen that appears in the emulator, click the Say Hello button.

Button Clicked - Emulator

Setting up and adding tests

One great advantage of scaffolding a new React Native app with the CLI tool is that a basic testing setup has already been configured in the seed project. The testing setup uses Jest, and includes a __tests__ folder to store the test suites. For this tutorial, though, we need to use the React Native testing library.

Install the React Native testing library as a development dependency:

npm install —-save-dev @testing-library/react-native

After installing the library, replace the code in the test suite __tests__/App-test.js file with this snippet:

import "react-native";
import React from "react";
import App from "../App";

import { fireEvent, render, waitFor } from "@testing-library/react-native";

it("Renders Message", async () => {
  const { getByTestId, getByText, queryByTestId, toJSON } = render(<App />);

  const button = getByText("Say Hello");
  fireEvent.press(button);

  await waitFor(() => expect(queryByTestId("printed-message")).toBeTruthy());

  expect(getByTestId("printed-message").props.children).toBe("Hello Tester");
  expect(toJSON()).toMatchSnapshot();
});

This code includes a single test for the message display behavior of the application. The React Native testing library is used to render the application’s root component App which contains the application logic. The button is then referenced and the click event is triggered on it using the press method of the fireEvent object from the testing library.

Because we added the asynchronous behavior to the response to the button click, we need to wait for the Text component, given a test id of printed-message, to display before testing its content.

Once it is loaded, the Text component is tested for the string Hello Tester.

To run the test suite, go to the root of the project and run:

npm run test

This command displays a screen in your CLI indicating that the tests passed.

Tests passed - CLI

Setting up the project on CircleCI

To begin the test automation process, the first step is pushing your project to GitHub.

Make sure that the GitHub account you are using is the one connected to your CircleCI account.

Now, go to the Projects page on the CircleCI dashboard. Select the GitHub account you are using for this project.

Add Project - CircleCI

Click Set Up Project.

Add Config - CircleCI

On the setup page, click Use Existing Config. Next, you get a prompt to either download a configuration file for the CI pipeline or start building.

Build Prompt - CircleCI

Click Start Building to begin the build. This build will fail because you have not written the CI pipeline configuration file yet. Writing that file is our next step.

Writing the CI pipeline

The continuous integration pipeline ensures that tests run whenever updates are pushed to the GitHub repository. The first step is creating a folder named .circleci at the root of the project. Add a configuration file named config.yml. In this file, enter:

version: 2.1
jobs:
  build:
    working_directory: ~/repo
    docker:
      - image: circleci/node:10.16.3
    steps:
      - checkout
      - run:
          name: Update NPM
          command: "sudo npm install -g npm@5"
      - restore_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
      - run:
          name: Install Dependencies
          command: npm install
      - save_cache:
          key: dependency-cache-{{ checksum "package-lock.json" }}
          paths:
            - ./node_modules
      - run:
          name: Run tests
          command: npm run test

In this pipeline configuration, the Docker image is first imported with the required Node.js version. Then, npm is updated and dependencies are installed, caching them so that subsequent builds are faster.

Finally, the npm run test command runs all tests contained in the project.

Save this file and commit all your changes to your remote repository. Commiting the changes triggers the pipeline to run successfully.

Build Success - CircleCI

Click build to show the pipeline build steps. If you collapse Run tests, you will see the details of the test.

Build Results - CircleCI

Fantastic!

Conclusion

Proper testing may be even more important for mobile apps than it is for web applications. Requiring users to install updates to your app every time you fix a bug is an easy way to annoy them. Keep users happy by applying what you have learned here to thoroughly test your React Native applications. More importantly, automate your testing process, and make sure those bugs are not pushed to your production code in the first place.

Happy Coding!


Fikayo Adepoju is a Full-stack developer, technical writer, and tech content creator proficient in Web and Mobile technologies and DevOps with over 10 years experience developing scalable distributed applications. With over 40 articles written for CircleCI, Twilio, Auth0, and The New Stack blogs and also on his personal Medium page, he loves to share his knowledge to as many developers as would benefit from it. You can also check out his video courses on Udemy.