Continuous integration for React Native applications
Fullstack Developer and Tech Author
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:
- Basic knowledge of Javascript
- Node.js (version >= 10.13) installed on your system
- A CircleCI account
- A GitHub account
- 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:
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.
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.
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
.
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.
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!