This tutorial covers:
- Creating and setting up a React Native application
- Writing tests for your application
- 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:
- 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)
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:
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.
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.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.
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
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.
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.