This tutorial covers:

  1. Creating and setting up a React Native application
  2. Writing tests for your application
  3. Building a continuous integration pipeline to run tests

Since its release in 2009, Apache Cordova has created a paradigm shift for mobile application development. Before Cordova, only those 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 argue that these apps are 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.

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

Our tutorials are platform-agnostic, but use CircleCI as an example. If you don’t have a CircleCI account, sign up for a free one here.

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.

To start the project on a simulator, run the following command from the terminal within the newly created project:

npx react-native run-ios --simulator="iPhone 13"

The preceding command will build the project and run it on an iPhone 13 simualtor as shown below:

virtual device running - Xcode

You leave the app running in its own terminal window. It will automatically refresh once you make changes to the code using Metro. Metro is a Javascript bundler that enables reloads of your application in the emulator any time file changes occur.

In this tutorial, you will be creating a simple React Native application that gives users a button to click and 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 {
  SafeAreaView,
  StatusBar,
  StyleSheet,
  Text,
  useColorScheme,
  Button,
  TextInput,
  View,
} from "react-native";

import { Colors } from "react-native/Libraries/NewAppScreen";

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

  const isDarkMode = useColorScheme() === "dark";
  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <StatusBar
        barStyle={isDarkMode ? "light-content" : "dark-content"}
        backgroundColor={backgroundStyle.backgroundColor}
      />

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

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.

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 i -D @testing-library/react-native@11.0.0

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

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: cimg/node:18.10.0
    steps:
      - checkout
      - restore_cache:
          key: dependency-cache-{{ checksum "yarn.lock" }}
      - run:
          name: Install Dependencies
          command: npm install
      - 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 push 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. From the list of projects, search for the project created in this tutorial and click Set Up Project

Select project

From the modal enter the name branch where your configuration file is located and click Set Up Project

This will trigger the pipeline and run successfully. Build success

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.

The complete source code for this tutorial can be found here on GitHub.

Happy coding!


Fikayo Adepoju is a LinkedIn Learning (Lynda.com) Author, 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.

Read more posts by Fikayo Adepoju