TutorialsLast Updated Jul 29, 20245 min read

Continuous integration for React Native applications

Fikayo Adepoju

Fullstack Developer and Tech Author

Developer A sits at a desk working on a beginning-level project.

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

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, enter y and 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. If it’s your first time creating a React Native project, expect the installation of dependencies to take some time.

To start the project on a simulator, cd into the new project directory and run the following command from the terminal within the newly created project:

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

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

virtual device running - Xcode

You can leave the app running in its own terminal window. It will automatically refresh using Metro once you make changes to the code. 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.tsx 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.

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

App running - 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.tsx 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 CircleCI dashboard. From the organization homepage, select Create Project. Select GitHub, then select your project from the dropdown, give it a meaningful name, and click Create Project.

Select Project - CircleCI

CircleCI will automatically detect your configuration file, but it won’t run the pipeline automatically. To trigger your first pipeline run, push a small, meaningless commit.

This will trigger the pipeline and run successfully.

Build Success - 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.

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.

Copy to clipboard