0% found this document useful (0 votes)
49 views

Testing React Native Apps eBook by Codemagic

Uploaded by

Nadir Hussain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
49 views

Testing React Native Apps eBook by Codemagic

Uploaded by

Nadir Hussain
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 63

Testing

React Native
Apps

by Nevercode
Testing
React Native Apps
by Codemagic and Sneh Pandya

Copyright © 2021 by Codemagic

All rights reserved. This publication text may not be uploaded or posted online without the prior
written permission of the publisher. For permission requests, write to the publisher, addressed
“Ebook permissions request”: liina@nevercode.io

Book authors: Sneh Pandya and Codemagic


Editors: Shane Lewans and Liina Laul
Front cover designed by Kätrin Sibul
Table of contents

Introduction 2

1. REACT NATIVE TESTING: TYPES OF TESTS 3


The testing pyramid 3

2. UNIT TESTING FOR REACT NATIVE 5


Testing React Native apps with Jest and Codemagic 7
Testing custom views and components in React Native 12

3. FUNCTIONAL TESTING FOR REACT NATIVE 21


React Native testing using Appium 22
React Native testing using Calabash 28
React Native testing using Espresso for Android 29
React Native testing using XCTest for iOS 30
How to run React Native Detox tests on Codemagic 32
Testing React Native apps with Linux instances on Codemagic 37

4. INTEGRATION TESTING FOR REACT NATIVE 43


Testing local databases for React Native 46
Testing API calls in React Native 50

Conclusion 59

codemagic.io 1
Introduction

Mobile apps play a crucial role in people’s lives and, as a result, directly impact
businesses. The bar is set high for all mobile apps in the market. By the end of 2021,
there will be roughly seven billion mobile users worldwide, according to a survey by
Statista. This exponential growth in the adoption of mobile apps can be attributed to
the increasing popularity of hybrid or cross-platform apps over native platforms.

Since 2015, React Native has consistently gained popularity and emerged as a popular
choice in the cross-platform mobile application space, as it saves between 40 and 90
percent of time spent developing apps on different platforms.

There were 218 billion mobile app downloads in 2020. That’s a seven percent increase
year over year, according to App Annie, an independent mobile statistics and analytics
research firm. Developing and maintaining an efficient app is a complicated process
and requires the investment of a significant amount of time and money.
Improvements, periodic updates and fixes are critical for the overall success of an app
in the long run.

According to Google, 25 percent of mobile users abandon a product after only one use,
whereas a whopping 70 percent of users abandon an app because it takes too long to
load. As an application grows and the codebase expands, small errors and edge cases
you don’t expect can cascade into larger failures.

Any mobile app needs to be thoroughly tested. Performance factors like loading times,
crashes, and complicated input fields all add up to a poor user experience and can ruin
the chances of market success. According to Instabug, 67 percent of bugs are reported
on iOS devices only. Bugs lead to bad user experiences and, ultimately, business losses.
One way to prevent fragile programming is to test your code thoroughly before
releasing it into the wild.

This ebook acts as a getting-started primer on the world of testing for apps built using
the React Native framework. The topics in this ebook cover a wide variety of
fundamental concepts and how-tos.

codemagic.io 2
React Native testing: Types of tests

There are multiple types of tests for applications that ensure a sophisticated approach
and confidence in delivering apps to the end users. But what are the differences
between them? Let’s find out!

The testing pyramid

Production-ready apps require rigorous testing before they go into production.


Software testing approaches have matured over time alongside evolving software.
Instead of having myriad manual software testers, development teams have moved
towards automating their testing efforts. This allows teams to know whether their
apps are broken in a matter of seconds or minutes instead of days or weeks.

The drastically shortened feedback loop supercharged by automated tests goes hand
in hand with continuous integration, continuous delivery and DevOps culture. If you
want to get serious about automated tests for your apps, you need to know the concept
of the test pyramid. Mike Cohn came up with this concept in his book Succeeding with
Agile.

codemagic.io 3
The three primary types of layers in the testing pyramid are:

● Unit tests: Unit testing ensures that each part of the code delivers the desired
output. Developers only look at the interface and the specifications of the
components. Thorough standalone testing of each component is performed
before moving on to the next unit.

The foundation of your test suite will be made up of unit tests. Your unit tests
make sure that a certain unit (the subject being tested) of your codebase works
as intended. Unit tests have the narrowest scope of all the tests in your test
suite. The number of unit tests in your test suite will largely outnumber any
other type of test.

● Functional tests: Functional testing is performed to verify the functionality


and/or usability of a component, but it is not restricted to only these.
Components can be anything that can take input(s) and deliver some output –
for example, a code module, a webpage, a screen and even a system inside a
larger system.

● Integration tests: Integration testing is performed to test individual


components to check how they function together. It involves testing the
integration between modules that are working fine individually. Integration
testing provides clarity about the overall functionality of the application.

All non-trivial applications will integrate with some other parts (e.g., databases,
filesystems, network calls to other applications). When writing unit tests, these are
usually the parts you leave out in order to come up with better isolation and faster
tests. Still, your application will interact with other parts, and these interactions need
to be tested. Integration tests are there to help. They test the integration of your
application with all the parts that live outside of your application.

We will dive deeper into each category in this book.

codemagic.io 4
Unit testing for React Native

By default, React Native provides Jest, a framework for unit testing that works for both
Android and iOS. Currently, test coverage isn’t perfect, but according to Facebook,
more unit-testing capabilities will be introduced in React Native, and users can
already build their own.

Every test case starts from a describe() function call, similar to how JUnit uses the
TestCase class. The describe() function takes two parameters: the title description and
the function to be executed. The it() function includes all of the test steps and returns
a series of expect() functions.

Below is an example:

describe("Music", function() {
var music;
var song;

beforeEach(function() {
music = new Music();
song = new Song();
});

it("should be able to play a song", function() {


music.play(song);
expect(music.currentlyPlayingSong).toEqual(song);

// demonstrates use of custom matcher


expect(music).toBePlaying(song);
});

describe("when song is paused", function() {


beforeEach(function() {
music.play(song);
music.pause();
});

it("should indicate the song is paused", function() {


expect(music.isPlaying).toBeFalsy();

codemagic.io 5
// demonstrates use of 'not' with a custom matcher
expect(music).not.toBePlaying(song);
});

it("should be possible to resume", function() {


music.resume();
expect(music.isPlaying).toBeTruthy();
expect(music.currentlyPlayingSong).toEqual(song);
});
});

// demonstrates use of spies to intercept and test method calls


it("shows the current song whether the user has made it a favorite",
function() {
spyOn(song, 'persistFavoriteStatus');

music.play(song);
music.makeFavorite();

expect(song.persistFavoriteStatus).toHaveBeenCalledWith(true);
});

// demonstrates use of expected exceptions


describe("#resume", function() {
it("should throw an exception if song is already being played",
function() {
music.play(song);

expect(function() {
music.resume();
}).toThrow("song is already being played");
});
});
});

This example shows how Jasmine can be used to test the functionality, but it keeps the
focus on method-level testing. Also, the React Native framework provides extra
capabilities for testing integrated components. This works for both native and
JavaScript components, enabling seamless communication between them via a bridge.

codemagic.io 6
Testing React Native apps with Jest and Codemagic
Jest is a testing framework built using JavaScript and maintained by Facebook.
Facebook itself uses the Jest testing framework to test its own React Native apps.

For React Native version 0.38 and above, Jest is included when you create a new
project, so the following setup may already be completed for you.

Before testing a React Native app with Jest, let’s start by configuring the React Native
project.

Configure React Native project

1. For our setup, we will install the Jest library:

npm i jest --save-dev


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

2. Add the details in the package.json file, as shown below:

"scripts": {
"test": "jest"
},
"jest": {
"preset": "jest-expo",
"setupFiles": ["<rootDir>/testing/jest-setup.js"]
}

Let’s say your app has one stack navigator and two screens. Your app would look
something like this:

import React from 'react';


import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

codemagic.io 7
export const Screen1 = ({ navigation }) => (
<View style={styles.container}>
<Text>Screen 1</Text>
<Button title="Go to Screen 2" onPress={() =>
navigation.push('Screen2')} />
<StatusBar style="auto" />
</View>
);

export const Screen2 = () => (


<View style={styles.container}>
<Text>Screen 2</Text>
<StatusBar style="auto" />
</View>
);

const Stack = createStackNavigator();


export const AppStack = () => (
<Stack.Navigator>
<Stack.Screen name="Screen1" component={Screen1} />
<Stack.Screen name="Screen2" component={Screen2} />
</Stack.Navigator>
);

...
...

const styles = StyleSheet.create({


container: {
flex: 1,
backgroundColor: '#f7f7f7',
alignItems: 'center',
justifyContent: 'center',
},
})

codemagic.io 8
React Native testing: Writing React Native integration tests

Let’s write our integration test. You don’t need to know how the app is implemented
since you want to test the app from a black-box testing perspective. Here’s what you
need to know:
● Screen1 is our initial screen in the app, which has a navigation function in
place.

● The user should be able to navigate to Screen2 when the button is pressed.

Here’s what the App.test.js file looks like:

import React from 'react';


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

import App, { Screen1, AppStack } from './App';

describe('Screen1', () => {
it('navigates on button press', () => {
const push = jest.fn();
const { getByText } = render(<Screen1 navigation={{ push }} />);
fireEvent.press(getByText('Go to Screen 2'));
expect(push).toHaveBeenCalledWith('Screen2');
});
});

Pretty straightforward test, right? The only thing specific to React Navigation that we
had to implement here is passing a navigation property to the component when we
render it.
Next, we will create a testing setup file called testing/jest-setup.js:

import 'react-native-gesture-handler/jestSetup';

jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock');
Reanimated.default.call = () => {};

codemagic.io 9
return Reanimated;
});

Running React Native integration tests

In your terminal, simply run npm test to run the test for React Navigation with Jest
that you just wrote.

You should also see an output saying:

PASS ./App.test.js
Screen 1
✓ navigates on button press (40ms)
AppStack
✓ renders the correct screen (32ms)

Test Suites: 1 passed, 1 total


Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.64s, estimated 2s
Ran all test suites related to changed files.

Running tests on Codemagic

Codemagic is a popular CI/CD solution for mobile apps. One of its coolest features is
running automated tests whenever a new commit or pull request is pushed in your Git
repository. To set up your React Native app on Codemagic, you can follow the steps
provided here.

codemagic.io 10
Once the project setup has been completed on Codemagic, you need to add the testing
configuration in the codemagic.yaml file, as shown below, for the tests to be executed:

workflows:

...

scripts:
- name: Install npm dependencies
script: npm install

...

- name: Tests # Insert before the build command


script: npm test

...

- name: Build ipa for distribution


script: xcode-project build-ipa --workspace
"$FCI_BUILD_DIR/ios/$XCODE_WORKSPACE" --scheme $XCODE_SCHEME

- name: Build Android app


script: cd android && ./gradlew assembleRelease

...

Once codemagic.yaml is updated, you are ready to run your tests and build on
Codemagic.

Codemagic can be configured to start the build process automatically after a commit
or a pull request using build triggers. For now, we will trigger the build manually to
start the tests and build process.

Just click on the Start new build button to trigger your build. Now, wait for the build to
complete, and it will show you the step results, including the test run.
Jest is a popular choice when writing test cases for React Native. You can learn more
about React Native testing with Jest here.

codemagic.io 11
Testing custom views and components in React
Native

Custom components and views are developed to implement custom functionality in


apps. These components are popular within the developer ecosystem because they can
be used to beautify apps and provide tailored experiences to the end users.

Nowadays, every app has unique elements, including UI, UX and even functional
perspectives. To keep the experience robust and smooth, it is essential to test even the
tiniest components, especially those with custom behaviors. Let’s dive into testing
custom reusable components in React Native.

Set up React Native application

Let’s get started by setting up our project. To create a new application, run the
commands as follows:

npm install -g expo-cli // To add Expo CLI on your machine

npx react-native init ReactNativeComponentTesting // To set up a new React


Native application

cd ReactNativeComponentTesting

Next, we will install testing dependencies, such as jest and react-test-renderer.

Let’s install the dependencies by running the command below in the terminal:

npm install react-test-renderer // React Test Renderer

npm install --save-dev jest // Jest

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


@testing-library/jest-native // React Native Testing library

codemagic.io 12
Create a reusable custom component

Let’s write a reusable React Native Button and test it. Suppose the reusable Button
component contains the specifications below:

● Fixed height and width with density-independent pixels

● Primary buttons should have a blue background and white text

● Secondary buttons should have a red background and white text

We also need to add property controls to this button for greater usability, flexibility
and unplanned changes. The newly added properties would be:

● ID: unique identifier in the app to connect business logic

● onPress: a function will be triggered on button press

Let’s have a look at how the component will be built:

import react from "react";


import { TouchableOpacity, Text } from "react-native";

export default function Button(props) {

// property declaration
const { title, onPress, primary, secondary, height, width } = props;

return (
<TouchableOpacity onPress={onPress}>
<Text>{title}</Text>
</TouchableOpacity>
);
}

Now, with the default function in place, let’s add styling and different variants.

import React from 'react';


import {TouchableOpacity, Text} from 'react-native';

codemagic.io 13
export default (Button = props => {

// added property declarations


const { title, onPress, secondary, large, height, width, baseColor,
textColor } = props;

if (!title) return new Error('No title added!');

// added conditions
const HEIGHT = large ? 60 : height ? height : 40;
const WIDTH = width ? width : 200;
const BACKGROUND_COLOR = secondary ? 'red' : baseColor ? baseColor :
'blue';
const TEXT_COLOR = textColor ? textColor : 'white';

return (
<TouchableOpacity
// added styling
style={{
alignItems: 'center',
justifyContent: 'center',
backgroundColor: BACKGROUND_COLOR,
height: HEIGHT,
width: WIDTH,
}}
onPress={onPress}>
<Text style={{color: TEXT_COLOR}}>{title}</Text>
</TouchableOpacity>
);
});

The above code snippet has added property declarations and conditions. Let’s look at
them in detail below:

● For height: If large is true, then set HEIGHT to 60. If height is true, then set
HEIGHT as height, or else set height to 40.

● For width: If width is true, then set width as width, or else set width to 200.

codemagic.io 14
● For BACKGROUND_COLOR: If secondary is true, then set BACKGROUND_COLOR to
red. If baseColor is true, then set BACKGROUND_COLOR as baseColor, or else set
BACKGROUND_COLOR to blue.

● For TEXT_COLOR: If TEXT_COLOR is true, then set TEXT_COLOR as textColor, or


else set TEXT_COLOR to white.

Set up reusable component in React Native

With the growing number of properties and features in the application, even simple
reusable components like Button can become complicated as a result of mutations and
possible combinations. Let’s have a look at how we would use this component in our
app:

import React from 'react';


import {View, Text, Dimensions, Alert} from 'react-native';

import Button from './src/Button';

const {height, width} = Dimensions.get('screen');

const App = () => {


return (
<View
style={{height, width, alignItems: 'center', justifyContent:
'center'}}>
{/* primary button */}
<Text>Primary</Text>
<Button title="Test Button" />

{/* large primary button */}


<Text>Primary Large</Text>
<Button title="Test Button" large />

{/* secondary button */}


<Text>Secondary</Text>
<Button title="Test Button" secondary />

codemagic.io 15
{/* secondary button */}
<Text>Secondary Large</Text>
<Button title="Test Button" secondary large />

{/* button with custom width and height */}


<Text>custom width and height</Text>
<Button title="Test Button" height={100} width={300} />

{/* button with custom baseColor and custom textColor */}


<Text>Custom colors</Text>
<Button title="Test Button" baseColor="lightpink" textColor="purple"
/>

{/* button with alert callback function */}


<Text>with onPress callback</Text>
<Button
title="Test Button"
onPress={() => Alert.alert('Button pressed')}
/>
</View>
);
};

export default App;

Execute tests with Jest

It’s time to set up the testing configuration for our code. We have already installed all
the required dependencies while setting up the application. Create a new file called
Button.test.js, and add the test case as shown below:

import 'react-native';
import React from 'react';
import renderer from 'react-test-renderer';

import Button from './Button';

describe('Testing custom button', () => {

codemagic.io 16
const wrapper = renderer.create(<Button title="Test Button" />);

it('Should render', () => {


expect(wrapper.toJSON()).toBeTruthy();
});
});

At the top of the file, the renderer is imported from react-test-renderer, which
provides a container (or wrapper) for our custom component.

describe marks the start of a new test suite in Jest. The first argument is a String
containing a description of what the encompassing test suite is testing. The second
argument is a callback function where the test execution steps are written.

it defines the start of a new test case in Jest. Tests should be as small and concise as
possible, and they should only test one thing. The arguments are the same as those of
describe – a String defining the test scenario and a callback function.

The .toJSON() and the .toBeTruthy() assertion function provided by Jest are
written inside of the it block. This will check if the value is not null or undefined.

To test the custom reusable Button, the different use cases can be:

● Primary button: height 40, width 200, baseColor blue, textColor white

● Secondary button: height 40, width 200, baseColor red, textColor white

● Size: button can be large or custom width

● Custom baseColor: can be applied to both the primary and secondary button

● Custom textColor: can be applied to both the primary and secondary button

Below is the test suite for the primary button. For brevity, the test suite of the
secondary button is skipped because the methods for the secondary button are the
same.

import 'react-native';
import React from 'react';

codemagic.io 17
import renderer from 'react-test-renderer';

import Button from './Button';

describe('Testing primary button', () => {


const wrapper = renderer.create(<Button title="Test Button" />);

console.log(wrapper.toJSON())

const styles = wrapper.toJSON().props.style;

const { height, width, backgroundColor } = styles;

const childStyles = wrapper.toJSON().children[0].props.style;

const {color: buttonTextColor} = childStyles;

it('Should render', () => {


expect(wrapper.toJSON()).toBeTruthy();
});

it('Should have height of 40', () => {


expect(height).toBe(40);
});

it('Should have width of 200', () => {


expect(width).toBe(200);
});

it('Should have blue background', () => {


expect(backgroundColor).toBe('blue');
});

it('Should have white text', () => {


expect(buttonTextColor).toBe('white');
});
});

Let’s say we want to test that our textColor is what we expected. The line
console.log(wrapper.toJSON()) gives the output shown below. This is useful for
testing our style and other elements, like children.

codemagic.io 18
{
type: 'View',
props: {
accessible: true,
style: {
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'blue',
height: 40,
width: 200,
opacity: 1
}
...
children: [ { type: 'Text', props: [Object], children: [Array] } ]
}
}

Let’s look at the next example of testing that the textColor is specified as we
expected. In the log above, children is an array of all the children seen from the root
node in the render tree. Moreover, there is only one child in this case, and the styles
can be fetched from children with the line of code below:

console.log('Children styles:=>',
wrapper.toJSON().children[0].props.style);

children[0]returns the first object in the array, and props.style returns the style
object. The output would look like this:

Children styles:=> { color: 'white' }

Since the structure becomes clearer with the above output, we can write the
declaration and the same type of test case as shown above:

// declaration
const { color: buttonTextColor } = childStyles;

// test case
it('Should have white text', () => {
expect(buttonTextColor).toBe('white');

codemagic.io 19
});

Once these techniques are understood, writing tests using Jest and React Native
becomes easier for all the other combinations of custom reusable Buttons. Run the
test using the command below:

yarn run test

Voila! The tests are written and successfully tested, with all tests passing!

codemagic.io 20
Functional testing for React Native

Multiple open-source test-automation frameworks are available for streamlining the


app development process and maximizing testing coverage. The best choice is a
framework that can support multiple platforms and provide a robust foundation for
test automation.

A range of platform-specific frameworks are also available to choose from. Naturally,


each framework is built for a particular platform, and in most cases, it is easier to
adopt for a specific platform.

Let’s take a look at the simple example below:

<Radio onSelect={this.onSelect.bind(this)}
defaultSelect={this.state.optionSelected - 1}>
<Option color="blue" selectedColor="#0000FF">
<Item title="First option" description="First radio button"/>
</Option>
<Option color="blue" selectedColor="#0000FF">
<Item title="Second option" description="Second radio button"/>
</Option>
<Option color="blue" selectedColor="#0000FF">
<Item title="Third option" description="Third radio button"/>
</Option>
</Radio>

The test snippets included for each framework section explain how the scripts deal
with UI elements and how clicks and other user inputs are handled. The purpose of the
examples is to compare and show the different options that are available for test
automation and what programming languages can be used for testing.

codemagic.io 21
React Native testing using Appium

Appium is an open-source test-automation framework for cross-platform


applications and mobile applications. When it comes to UI testing, Appium is a great
option for testing React Native apps. React Native apps are testable out of the box with
Appium – perfect for mobile testing.

Let’s learn how Appium can be used for React Native testing.

Set up a new project

To begin, create a new project for React Native using the command below:

react-native init ReactNativeAppiumTest

Now, install Appium through npm or as a standalone app.

npm install -g appium

The standalone version can be downloaded from here.

Next, create a separate directory to write test cases. Ideally, it should be inside the
project’s root folder to keep everything in place. Inside the tests folder, create a
package.json file.

WebdriverIO is an open-source testing utility for NodeJS that supports Appium.

To install WebdriverIO, run the command below:

npm install --save webdriverio @wdio/cli

Next, install ChaiJS as shown below to include support for assertion statements while
writing test cases:

npm install --save chai

codemagic.io 22
Configure WebdriverIO

The web driver config file needs to be generated to apply the configuration while
testing. Run the command below inside the project:

npx wdio config

In return, the command prompt will ask a series of questions and install the required
dependencies based on the input selections.

Note: You need to select both the Mocha and Appium services when configuring via
the command line.

Once this is done, the wdio.conf file will be generated inside the tests directory.

Perform the following changes to configure WebdriverIO to work with Appium and
run tests on Android Emulator:

exports.config = {
services: ['appium'],
port: 4723,
runner: 'local',
specs: [
'./tests/specs/**/*.js'
],
capabilities: [{
maxInstances: 1,
browserName: '',
appiumVersion: '1.13.0',
platformName: 'Android',
platformVersion: '<emulator platform version>',
deviceName: '<emulator name>',
app: '<path to APK>',
automationName: 'UiAutomator2'
}],

logLevel: 'trace',
bail: 0,
waitforTimeout: 10000,
connectionRetryTimeout: 90000,

codemagic.io 23
connectionRetryCount: 3,
framework: 'mocha',
reporters: ['spec'],
mochaOpts: {
ui: 'bdd'
timeout: 60000
}
}

In this file, Appium is added to the service list, and the default port for communication
is also set. Declare the APK path, Android emulator name and version to complete the
configuration. Also, maxInstances is set to 1 to avoid running multiple tests in
parallel.

Preparing demo application

Let’s prepare a simple application for the purpose of React Native testing using
Appium. In our case, we will add a simple login page to run tests using Appium. Inside
the App.js file, add the code below:

import React, { Component } from 'react';


import { TouchableHighlight, StyleSheet, Text, TextInput, View } from
'react-native';

export default class App extends Component {


constructor() {
super()
this.state = {
username: '',
password: '',
isLogined: false
}
}

inputChangeHandler = (value, name) => {


this.setState({
[name]: value
})
}

codemagic.io 24
login = () => {
if ((this.state.username == 'codemagic') && (this.state.password ==
'nevercode')) {
this.setState({ isLogined: true });
}
else {
this.setState({ isLogined: false });
}
}

render() {
return (
<View style={LOCAL_STYLES.wrapper} testID="app-root"
accessibilityLabel="app-root">
<View style={LOCAL_STYLES.inputContainer}>
<TextInput name="username" accessibilityLabel="username"
style={LOCAL_STYLES.input} onChangeText={(text) =>
this.inputChangeHandler(text, "username")} />
</View>

<View style={LOCAL_STYLES.inputContainer}>
<TextInput name="password" accessibilityLabel="password"
secureTextEntry={true} style={LOCAL_STYLES.input} onChangeText={(text) =>
this.inputChangeHandler(text, "password")} />
</View>

<Text accessibilityLabel="loginstatus">{this.state.isLogined ?
"success" : "fail"}</Text>

<TouchableHighlight style={LOCAL_STYLES.buttonContainer}
accessibilityLabel="login" onPress={this.login}>
<Text style={{ color: 'white' }}>Login</Text>
</TouchableHighlight>
</View>
);
}
}

const LOCAL_STYLES = StyleSheet.create({


wrapper: {
flex: 1,
alignItems: "center",

codemagic.io 25
justifyContent: "center"
},

inputContainer: {
borderBottomColor: '#AFAFAF',
backgroundColor: '#FFFFFF',
borderRadius: 10,
borderBottomWidth: 1,
marginBottom: 16,
flexDirection: 'row',
alignItems: 'center',
width: '80%',
borderColor: 'blue',
borderWidth: 1
},
buttonContainer: {
height: 45,
width: 250,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 20,
borderRadius: 20,
backgroundColor: "#00b5ec"
}
});

To make the components testable, add a property to the relevant components. For
Android, add accessibilityLabel, and for iOS, add testID, as shown in the code
above.

Now run the app and check the screen. A simple login screen should open up. Next,
let’s write test cases using Appium to verify our login functionality.

Preparing the test case

Inside the tests directory, create a new file named App.test.js, and add the code
below:

codemagic.io 26
var expect = require('chai').expect;

describe('Simple App testing', () => {

beforeEach(() => {
$("~app-root").waitForDisplayed(10000, false)
});

it('Login test: valid case', async => {


$('~username').setValue("codemagic");
$('~password').setValue("nevercode");

$("~login").click();

$("~loginstatus").waitForDisplayed(11000);
const status = $("~loginstatus").getText();
expect(status).to.equal('success');
});

it('Login test: invalid case', async => {


$('~username').setValue("nevercode");
$('~password').setValue("codemagic");

$("~login").click();

$("~loginstatus").waitForDisplayed(11000);
const status = $("~loginstatus").getText();
expect(status).to.equal('fail');
});
});

The above test case will verify the login functionality and check whether the test
passes or fails based on the input credentials. beforeEach is added to make sure the
app is loaded prior to the test being run.

Execute tests

To execute the test case written above, let’s launch an emulator with the command
below:

codemagic.io 27
react-native run-android

Now, let’s start the Appium server in a separate command prompt to set up
communication:

appium

In a new command prompt, run the WebDriver test suite using the command below:

npx wdio ./wdio.conf.js

Once set up, the test execution will start, and the command prompt will show the logs
of the executed steps in parallel when the emulator shows the test running.

Once the automation process is complete, you will see the test results as follows:

App testing
✓ Login test: valid case
✓ Login test: invalid case

2 tests passing (10.1s)

It is popular in the developer ecosystem to use Appium to test React Native apps.
Appium makes it seamless to write test cases for both the Android and iOS platforms
while working with React Native. Selenium, the underlying web driver, works as a
bridge between Appium and mobile platforms to deploy and execute tests.

React Native testing using Calabash


Calabash enables anyone to write tests for mobile applications. Calabash tests are
written in Gherkin and run in Cucumber. All tests are simple and easy to read yet
executable by the automation system. It is easier to write test cases because of the
straightforward vocabulary and specification-oriented language. An example is
shown below:

Feature: Answer the questions feature


Scenario: As a valid user, I want to answer the question,

codemagic.io 28
I wait for text "What is the best way to test a mobile application on a
thousand devices?"
Then I press the radio button 0
Then I press the radio button 1
Then I press the radio button 2
Then I enter the text "Sample Test" into field with id "editText1"
Then I press view with id "Button1"

Steps usually begin with one of the following keywords: given, then, when, and or but.

React Native testing using Espresso for Android


Espresso provides APIs for writing UI tests to simulate user interactions for Android
apps. The Espresso API is lightweight and provides three main components:
viewMatchers, viewActions and viewAssertions.

Espresso provides automatic synchronization of test methods and UI elements that


are being tested. This makes test execution extremely fast because no test scripts need
to include any sleep or wait commands. An example is shown below:

onView(withId(R.id.radio0)).perform(click());
onView(withId(R.id.radio1)).perform(click());
onView(withId(R.id.radio2)).perform(click());
onView(withId(R.id.EditText1)).perform(click());

onView(withId(getInstrumentation().getTargetContext().getResources()
.getIdentifier("com.example.app:id/edittext1", null,
null))).perform((typeText("Simple Test")));
onView(withId(getInstrumentation().getTargetContext().getResources()
.getIdentifier("com.example.app:id/button1", null,
null))).perform(click());

Espresso is a powerful framework that offers many additional services and function
calls to developers.

codemagic.io 29
React Native testing using XCTest for iOS
XCTest comes with Xcode but can also be used with both real iOS devices and
simulators. It allows developers to write tests for components at any level and also
provides a framework for UI testing capabilities. XCTest tests are grouped into
subclasses of XCTestCase. Writing tests using XCTest is trivial because it is fully
compatible with both Objective-C and Swift.

Here’s how UI components would look with Objective-C:

- (void)testClicksOnRadioButtons {
[tester tapViewWithAccessibilityLabel:@"Radio1"];
[tester tapViewWithAccessibilityLabel:@"Radio2"];
[tester tapViewWithAccessibilityLabel:@"Radio3"];

[tester enterText:@"Simple Test"


intoViewWithAccessibilityLabel:@"editText1"];

[tester tapViewWithAccessibilityLabel:@"Answer"];
}

Alternatively, with Swift, the test would look as simple as this:

testClicksOnRadioButtons() {
let app = XCUIApplication()

app.radiobutton[0].tap()
app.radiobutton[1].tap()
app.radiobutton[2].tap()

app.staticTexts["Simple Test"]

app.button[0].tap()
}

codemagic.io 30
How to run React Native Detox tests on Codemagic
The most difficult part of automated testing on mobile is performing end-to-end
(E2E) tests. These involve testing the app from the user’s perspective by running it on
a device (or emulator/simulator).

One of the major issues with other black-box testing libraries is that tests are usually
non-deterministic and flaky. We will take a look at one of the most popular
end-to-end testing libraries for React Native, called Detox.

Detox solves the problem of black-box testing by taking a gray-box end-to-end


testing approach. Let’s build a small React Native sample app called Hello Detox.

You can use the following command to create a new React Native app:

npx react-native init hello_detox

Let’s install the dependencies needed to set up Detox for your React Native project in
order to start writing Detox tests. You can do this with the commands below:

brew tap wix/brew


brew install applesimutils
npm install -g detox-cli

Navigate to your React Native root project directory, and run the following:

npx install detox --save-dev

codemagic.io 31
Configuring for Android

Go to your Android build.gradle (project), and update the minSdkVersion to 18.


Then update the same file as per the configuration below:

allprojects {
repositories {
maven {
// All of the Detox artifacts are provided via the npm module
url "$rootDir/../node_modules/detox/Detox-android"
}
}
}

Next, add the configuration below to the app-level build.gradle file.

android {
defaultConfig {
// Added these for running tests
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
}

dependencies {
// Added testing dependencies
androidTestImplementation('com.wix:detox:+') { transitive = true }
androidTestImplementation 'junit:junit:4.12'
}

Now, create a file called DetoxTest.java in the path


android/app/src/androidTest/java/com/[your_package]/ and add the following
code:

package [your_package];

codemagic.io 32
import com.wix.detox.Detox;
import com.wix.detox.config.DetoxConfig;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class DetoxTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new
ActivityTestRule<>(MainActivity.class, false, false);

@Test
public void runDetoxTests() {
DetoxConfig detoxConfig = new DetoxConfig();
detoxConfig.idlePolicyConfig.masterTimeoutSec = 90;
detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60;
detoxConfig.rnContextLoadTimeoutSec =
(com.[your_package].BuildConfig.DEBUG ? 180 : 60);

Detox.runTests(mActivityRule, detoxConfig);
}
}

Adding Detox configurations

You have to add configurations for using the detox-cli commands for building and
testing the app. Add the following to the .detoxrc.json file:

{
"testRunner": "jest",
"runnerConfig": "e2e/config.json",
"configurations": {

codemagic.io 33
"android.emu.debug": {
"type": "android.emulator",
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd android && ./gradlew assembleDebug assembleAndroidTest
-DtestBuildType=debug && cd ..",
"device": {
"avdName": "Pixel_API_29"
}
},
"android.emu.release": {
"binaryPath":
"android/app/build/outputs/apk/release/app-release.apk",
"build": "cd android && ./gradlew assembleRelease assembleAndroidTest
-DtestBuildType=release && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_API_29"
}
}
}
}

In the above JSON, replace the avdName with the name of the emulator or device that
you are using.

Configuring for iOS

You do not need to add additional dependencies for iOS, so configuring for iOS is
pretty straightforward. Just add the Detox configurations to the .detoxrc.json file:

{
"testRunner": "jest",
"runnerConfig": "e2e/config.json",
"configurations": {
"ios.sim.debug": {
"type": "ios.simulator",
"binaryPath":
"ios/build/Build/Products/Debug-iphonesimulator/hello_detox.app",

codemagic.io 34
"build": "xcodebuild -workspace ios/hello_detox.xcworkspace -scheme
hello_detox -configuration Debug -sdk iphonesimulator -derivedDataPath
ios/build",
"device": {
"type": "iPhone 11"
}
},
"ios.sim.release": {
"binaryPath":
"ios/build/Build/Products/Release-iphonesimulator/hello_detox.app",
"build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace
ios/hello_detox.xcworkspace -UseNewBuildSystem=YES -scheme hello_detox
-configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"device": {
"type": "iPhone 11"
}
}
}
}

Writing Detox tests

Jest is a recommended test runner for React Native, and we will use it for setting up
the Detox tests in our React Native app. Use the following CLI command provided by
Detox for initializing Jest:

detox init -r jest

This command will create an e2e folder in the root project directory with sample test
code. We will add a very simple test that checks whether a Text component is present
on the screen.

<Text testID="edit-text" style={styles.sectionDescription}>


Edit <Text style={styles.highlight}>App.js</Text> to change this screen
and then come back to see your edits.
</Text>

codemagic.io 35
Now, navigate to e2e/firstTest.e2e.js, and replace its content with the following:

describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative()
})

it('should have edit text on welcome screen', async () => {


await expect(element(by.id('edit-text'))).toBeVisible()
})
})

Running Detox tests on device

With the Detox test environment properly configured, now you are ready to run the
test on your device (or emulator/simulator). First, you have to perform a build of the
app before running the test.

For Android, you can build the app by using:

detox build -c android.emu.release

You can start the tests by using:

detox test -c android.emu.release

For iOS, you can build the app by using:

detox build -c ios.sim.release

You can start the tests by using:

detox test -c ios.sim.release

codemagic.io 36
Detox allows React Native apps to be tested with a focus on end-to-end functionality.
Hence, it is a powerful tool that every React Native developer should have under their
belt.

Testing React Native apps with Linux instances on


Codemagic

Did you know that Codemagic now supports Linux instances too? Here’s yet another
way to build and test React Native apps!

On Linux, you can build Android and other non-Mac workflows more quickly with
out-of-the-box support for Android emulators with hardware acceleration. You can
specify a Linux build machine by using linux or linux_x2 as the instance type in
codemagic.yaml.

Linux can only be used in codemagic.yaml configured workflows, so see the getting
started guide or YAML cheat sheet to migrate over any workflows you want to use on
Linux.

Let’s look at how to build and test React Native apps on Linux instances.

Configure React Native project

For our setup, we will install the Jest testing framework:

npm i jest --save-dev

codemagic.io 37
npm install @testing-library/react-native --save-dev
npm install @react-navigation/native --save-dev

Add the details in the package.json file, as shown below:

"scripts": {
"test": "jest"
},
"jest": {
"preset": "jest-expo",
"setupFiles": ["<rootDir>/testing/jest-setup.js"]
}

Let’s prepare a simple app that has two screens. Your app would look something like
this:

import React from 'react';


import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

export const HomeScreen = ({ navigation }) => (


<View style={styles.container}>
<Text>Home Screen</Text>
<Button title="Navigate to Detail Screen" onPress={() =>
navigation.push('DetailScreen')} />
<StatusBar style="auto" />
</View>
);

export const DetailScreen = () => (


<View style={styles.container}>
<Text>Detail screen</Text>
<StatusBar style="auto" />
</View>
);

const Stack = createStackNavigator();


export const AppStack = () => (

codemagic.io 38
<Stack.Navigator>
<Stack.Screen name="HomeScreen" component={HomeScreen} />
<Stack.Screen name="DetailScreen" component={DetailScreen} />
</Stack.Navigator>
);

Writing tests for React Native

Scenario 1: The Home Screen is the initial screen of the app, which has navigation
enabled. The user is able to navigate to the Detail Screen by tapping on the button.

import React from 'react';


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

import App, { HomeScreen, AppStack } from './App';

describe('HomeScreen', () => {
it('navigates on button press', () => {
const push = jest.fn();
const { getByText } = render(<HomeScreen navigation={{ push }} />);
fireEvent.press(getByText('Go to Detail Screen'));
expect(push).toHaveBeenCalledWith('Detail Screen');
});
});

Scenario 2: Let’s say you want to check your Detail Screen for a text box that collects
users’ emails for a newsletter. Jest will test the selected component in JSON format.

expect(findTextElement(tree, 'email')).toBeDefined();
})

codemagic.io 39
Running tests for React Native

We will now create a testing setup file called testing/jest-setup.js:

import 'react-native-gesture-handler/jestSetup';

jest.mock('react-native/Libraries/Animated/src/NativeAnimatedHelper');

jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock');
Reanimated.default.call = () => {};
return Reanimated;
});

Testing on a local machine is simple – just go to your terminal and simply run npm
test. This will run the tests you just implemented. You should be able to see an output
similar to this:

PASS ./App.test.js

Home Screen
✓ navigates on button press (40ms)

AppStack
✓ renders the correct screen (32ms)

Detail Screen
✓ email field found (94ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 1.62s, estimated 2s
Ran all test suites related to changed files.

codemagic.io 40
Running tests on Codemagic

Codemagic is a popular CI/CD solution that offers automated build pipelines to check
continuous integration and run tests. To set up your React Native app on Codemagic,
you can follow the steps provided here.

This time, we will build and test our React Native app on a newly available Linux
instance. Let’s set up a codemagic.yaml file to perform this.

workflows:
my-workflow:
name: My workflow name
instance_type: linux # specify linux or linux_2 for
Linux instances
max_build_duration: 60
...

scripts:
- name: Install npm dependencies
script: npm install

...

- name: Tests # Insert before the build


command
script: npm test

...

- name: Build Android app


script: cd android && ./gradlew assembleRelease

...

Once codemagic.yaml is updated, you are ready to run your tests and build on
Codemagic.

codemagic.io 41
Codemagic can be configured to start the build process automatically after a commit
or a pull request using build triggers. For now, we will trigger the build manually to
start the tests and build process.

Just click on the “Start new build” button to trigger your build. Now, wait for the build
to complete, and it will show you the step results, including the test run.

This is how you can build a React Native app on Codemagic’s Linux instance as well as
test your app. Codemagic provides simple, straightforward, out-of-the-box solutions
to achieve your tasks of building, testing and publishing apps without hassle.

codemagic.io 42
Integration testing for React Native

While performing React Native testing via integration tests, the communication goes
through the bridge and requires both native and JavaScript components to be in place.
Cavy is a test framework for React Native that is cross-platform and supports
integration tests.

Cavy makes use of React ref generating functions to provide the ability to refer to and
simulate actions for deeply nested components within an app. Cavy can run
seamlessly on a host device (e.g., Android or iOS simulator).
To get started, install Cavy using yarn:

yarn add cavy --dev

or npm:

npm i --save-dev cavy

For your app, you need to import Tester, TestHookStore and the specs in a top-level
JavaScript file, as shown below:

import React, { Component } from 'react';


import { Tester, TestHookStore } from 'cavy';
import AppSpec from './specs/AppSpec';
import App from './app';

const testHookStore = new TestHookStore();

export default class AppWrapper extends Component {


render() {
return (
<Tester specs={[AppSpec]} store={testHookStore} waitTime={4000}>
<App />
</Tester>
);
}
}

codemagic.io 43
Then the components need to be added by adding references and using the
generateTestHook() function. generateTestHook() takes a string as its first
argument and an optional second argument to set your own ref generating function.
Functional components cannot be assigned a ref since they do not hold any instances.

import React, { Component } from 'react';


import { TextInput } from 'react-native';
import { hook } from 'cavy';

class Scene extends Component {


render() {
return (
<View>
<TextInput
ref={this.props.generateTestHook('Scene.TextInput')}
onChangeText={...}
/>
</View>
);
}
}

const TestableScene = hook(Scene);


export default TestableScene;

Now, we can write the spec functions referencing the already hooked components.

export default function(spec) {


spec.describe('Feature', function() {
spec.it('works', async function() {
await spec.fillIn('Scene.TextInput', 'random string')
await spec.press('Scene.button');
await spec.exists('NextScene');
});
});
}

Then register the AppWrapper as the main entry point with AppRegistry instead of the
current App component, as shown below:

AppRegistry.registerComponent('AppWrapper', () => AppWrapper);

codemagic.io 44
Below is the available set of spec helpers:

// Fills in the identified component with the string. The component must
respond to onChangeText.
fillIn(identifier, str)

// Presses the identified component. The component must respond to onPress.


press(identifier)

// Pauses the test for this length of time in milliseconds. Useful if you
need to allow time for a response to be received before progressing.
pause(integer)

// Returns true if the component can be identified or is found on the


screen
exists(identifier)

// Returns true if the component cannot be identified or is not found on


the screen
notExists(identifier)

// Returns the identified component. Can be used if the component doesn't


respond to either onChangeText or onPress.
findComponent(identifier)

A good alternative for running unit tests and integration tests is Mocha, which
provides a feature-rich JavaScript test framework that runs on Node.js.

codemagic.io 45
Testing local databases for React Native

Databases are an integral part of mobile development applications. Although most


applications these days heavily rely on APIs, applications that have a database as an
essential component are here to stay.

One of the main goals of testing such components is to catch bugs earlier by running
the tests locally or through a process. Unit tests for components like databases can
help developers who may not have worked on the code to understand what it should be
doing when it executes.

In native Android, it’s common to test SQLite code in isolation. Let’s see how it is
possible to do this for React Native using the Jest testing framework.

Set up a React Native application

Let’s get started by setting up our project. To create a new application, run the
commands as follows:

npm install -g expo-cli // To add Expo CLI on your machine


npx react-native init ReactNativeDBTesting // To set up a new React Native
app
cd ReactNativeDBTesting

Next, we will use node-sqlite3 to test sqlite code using jest. It is compatible with
packages like react-native-sqlite2 and react-native-sqlite-storage.

Let’s install the dependencies by running the command below in the terminal:

npm install --saveDev sqlite3@4.1.1 @types/websql@0.0.27


@types/sqlite@3.1.6 // SQLite
npm install --save-dev jest // Jest
npm install --save-dev @testing-library/react-native
@testing-library/jest-native

codemagic.io 46
Make sure you are using the same SQLite version in both Jest and the application
environment.

Once successfully completed, you can verify that Jest has been added in the
package.json file with the configuration:

{
"scripts": {
"test": "jest"
},
"jest": {
"preset": "react-native"
}
}

Set up Jest for React Native

To use Jest, we need to follow a few steps to set up Jest in our React Native
application.

1. In the root directory of the project, add a new file and name it jest.config.js.
2. Within jest.config.js, add the code below:

module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
};

preset is a preset that is used as a base for Jest’s configuration. A preset should point
to an npm module that has a jest-preset.json or jest-preset.js file at the root.

setUpFilesAfterEnv specifies a list of paths to modules that run some code to


configure or set up the testing framework before each test file in the suite is executed.

codemagic.io 47
Define queries for tests

We will extract a query executor from the application’s environment for testing
purposes.

export type SqlExecutor = {


read: (query: string, args: Array<string>) => Promise<string[]>,
write: (query: string, args: Array<string>) => Promise<string[]>,
}

Now, let’s define queries using the read and write of the executor in
createUsers.ts, as shown below:

const CREATE_TABLE_IF_NOT_EXIST = `CREATE TABLE IF NOT EXISTS USERS (NAME


TEXT);`

const GET_COUNT = `SELECT count(*) AS result FROM USERS`

export const createUsers = (executor: SqlExecutor) => ({


createTable: () => executor.write(CREATE_TABLE_IF_NOT_EXIST),
getCount: () => executor.read(GET_COUNT).then(result =>
Number.parseInt(result[0]))
})

Writing tests for database

Before writing the test, we need to write an implementation of the executor.

const { promisify } = require('util');


import { Database } from 'sqlite3';
const createNodeSqlite2Executor = (db: Database): SqlExecutor => {
const allPromise = promisify(db.all).bind(db);
return {
read: ((query, args) => allPromise(query, args)),
write: ((query, args) => allPromise(query, args)),
};
};

codemagic.io 48
The executor returns a Promise with the result of the read and write query operations.

Next, let’s add the test configuration for the database, as shown below:

const sqlite3 = require('sqlite3').verbose();


import { createUsers } from "./createUsers"

const inMemoryDb = new sqlite3.Database(':memory:');


const nodeExecutor = createNodeSqlite2Executor(inMemoryDb);
const userQueries = createUsers(nodeExecutor);

describe('usersQueries', () => {
beforeEach(async () => {
await userQueries.createTable();
})
it('getCount should return zero for empty table', async () => {
const count = await userQueries.getCount();
expect(count).toEqual(0);
});
});

This test will describe the status of the Users table after communicating with the
actual local database.

Add configuration to React Native application

After writing the test for our database, we will implement SqlExtractor in our React
Native application.

const parseSql(rs: SQLResultSet): string[] => {


const result: Array<string> = new Array(rs.rows.length);
for (let i = 0; i < rs.rows.length; i += 1) {
result[i] = rs.rows.item(i);
}
return result;
}

codemagic.io 49
const executeSql = (
tr: SQLTransaction,
query: string,
...args: string[]
):Promise<string[]> => new Promise((resolve, reject) => {
tr.executeSql(
query,
args,
(_, result) => resolve(parseSql(result)),
(_, error) => {
reject(error);
return true;
},
);
});

export const createRnSqlite2Executor = (db: Database) : SqlExecutor => ({


read: (query, ...args: string[]) => new Promise(
(resolve) => db.readTransaction((tr) => resolve(executeSql(tr, query,
...args))),
),
write: (query, ...args: string[]) => new Promise(
(resolve) => db.transaction((tr) => resolve(executeSql(tr, query,
...args))),
),
});

Now we can use createRnSqlite2Executor in our React Native application (for


example, using the React Context API). This is how SQLite databases can be tested for
React Native apps.

Testing API calls in React Native

Along with React Native’s growing adoption, the importance of the stability of apps
built using React Native is also growing. The React Native framework provides testing
methods, but none of them thoroughly cover the actual logic written in the apps.
Therefore, React Native apps benefit more from functional testing.

codemagic.io 50
API calls are a crucial part of any application since the majority of functionalities rely
on network requests nowadays. Let’s look into how to perform functional testing for
APIs.

We will proceed with Jest, Enzyme and Axios Mock Adapter for this particular article
to test API calls in our React Native application. Jest and Enzyme act as the backbone
of the testing stack. We will use Enzyme to do our setup and simulate runtime
behavior. We will use axios-mock-adapter to mock our API calls in order to test them.

Setting up React Native application

Let’s get started by setting up our project. React Native creates directories for both
Android and iOS that are labeled android and ios, respectively. To create a new
application, run the commands as follows:

npm install -g expo-cli // To add Expo CLI on your machine

npx react-native init ReactNativeAPITesting // To set up a new React Native


app

cd ReactNativeAPITesting

The dependencies required for the project setup are shown below. Copy them into your
package.json file:

{
"main": "node_modules/expo/AppEntry.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject",
"test": "jest"
},
"jest": {
"preset": "jest-expo",
"collectCoverage": true,

codemagic.io 51
"transformIgnorePatterns": [

"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react
-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navig
ation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)"
],
"setupFilesAfterEnv": [
"<rootDir>src/setupTests.js"
],
"collectCoverageFrom": [
"**/*.{js,jsx}",
"!**/coverage/**",
"!**/node_modules/**",
"!**/babel.config.js",
"!**/jest.setup.js"
]
},
"dependencies": {
"@react-native-community/hooks": "^2.5.1",
"@react-native-community/masked-view": "0.1.6",
"@react-navigation/bottom-tabs": "^5.7.3",
"@react-navigation/native": "5",
"@react-navigation/stack": "^5.7.1",
"apisauce": "^1.1.2",
"babel-jest": "^26.3.0",
"babel-preset-react-native": "^4.0.1",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.4",
"expo": "~37.0.3",
"expo-cli": "^3.27.10",
"expo-constants": "^9.0.0",
"expo-image-picker": "~8.1.0",
"expo-location": "~8.1.0",
"expo-permissions": "~8.1.0",
"faker": "^5.1.0",
"formik": "^2.1.4",
"global": "^4.4.0",
"jest": "^26.4.2",
"jest-react-native": "^18.0.0",
"lottie-react-native": "~2.6.1",
"react": "~16.9.0",
"react-dom": "~16.9.0",
"react-native":

codemagic.io 52
"https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz",
"react-native-gesture-handler": "~1.6.0",
"react-native-reanimated": "~1.7.0",
"react-native-safe-area-context": "0.7.3",
"react-native-screens": "~2.2.0",
"react-native-web": "~0.11.7",
"react-test-renderer": "^16.13.1",
"yup": "^0.29.1"
},
"devDependencies": {
"@babel/core": "^7.8.6",
"babel-preset-expo": "~8.1.0",
"jest-expo": "^38.0.2"
},
"private": true
}

Next, install the dependencies using the command below:

npm install
// OR
yarn install

For brevity, we will create a simple application in this article.

import React from 'react';


import { StyleSheet, Text, TouchableOpacity } from 'react-native';
function AppButton({ onPress }) {
return (
<TouchableOpacity
style={[styles.button,
{ backgroundColor: colors[color] }]}
onPress={onPress} >
<Text style={styles.text}>Register</Text>
</TouchableOpacity>
);
}
const styles = StyleSheet.create({
button: {
backgroundColor: red;
borderRadius: 25,

codemagic.io 53
justifyContent: 'center',
alignItems: 'center',
},
text: {
color: #fff
}
})
export default AppButton;

Adding Jest to React Native application

In this section, we’ll test for user interaction and the UI of the app components by
testing snapshots using Jest. To achieve this, we need to install Jest and its
dependencies:

yarn add jest-expo --dev

Then, update your package.json file with the test script:

"scripts": {
"test" "jest"
},
"jest": {
"preset": "jest-expo"
}

Next, to perform comprehensive tests, execute the command below:

npm i react-test-renderer --save-dev

We need to add a transformIgnorePattern configuration that prevents tests from


running in Jest whenever a source file matches a test:

"jest": {
"preset": "jest-expo",

codemagic.io 54
"transformIgnorePatterns": [

"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react
-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navig
ation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)"
]
}

Let’s write a basic test first. Create a file named App.test.js, and add the details as
shown below:

import React from "react";


import renderer from "react-test-renderer";
import App from "./App.js"
describe("<App />", () => {
it('has 1 child', () => {
const tree = renderer.create(<App />).toJSON();
expect(tree.children.length).toBe(1);
});
});

Now run npm test or yarn test in your terminal. If App.js has a single child
element, the test will pass.

Calling APIs in React Native

To call APIs and test them, we are going to make use of mock functions. Mock
functions are copies of objects or a function without the real workings of that
function. Rather, it imitates that particular function. Mocks mainly reduce the need
for dependencies.

React Native includes fetch in the global object. To avoid making real API calls in our
unit test, we mock them. A way to mock is shown below:

global.fetch = jest.fn();

// mocking an API success response


fetch.mockResponseIsSuccess = (body) => {
fetch.mockImplementationForOnce (

codemagic.io 55
() => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))})
);
};

// mocking an API failure response


fetch.mockResponseIsFailure = (error) => {
fetch.mockImplementationForOnce(
() => Promise.reject(error)
);
};

The function tries to fetch an API once. Having done this, it returns a promise, and
when it is resolved, it returns the body in JSON. It’s similar to the mock response for a
failed fetch transaction – it returns an error. Below is a component of our app:

import React from 'react';


const Product = () => {
const product = {
name: 'Pizza',
quantity: 5,
price: '$50'
}
return (
<>
<h1>Name: {product.name}</h1>
<h1>Quantity: {product.quantity}</h1>
<h1>Price: {product.price}</h1>
</>
);
}
export default Product;

Now we will try to test all our components. Directly accessing our database is not a
feasible solution. Instead, we need to mock a component of the product by using Jest
to describe the objects in the component, as shown below:

import { mount } from 'enzyme';


describe("<App />", () => {
it("accepts products props", () => {
const wrapper = mount(<Customer product={product} />);
expect(wrapper.props().product).toEqual(product);

codemagic.io 56
});
it("contains products quantity", () => {
expect(value).toBe(3);
});
});

Enzyme is used in the above code snippet to perform the full rendering, which actually
mounts the component in the DOM using the mount keyword. This means that tests
can affect each other if they are all using the same DOM. While writing tests, you
should also use munmount or something similar to clean up the DOM if necessary.

Mocking APIs in React Native

Now let’s mock an external API call. To test an external call to an API, you need to
mock requests as well as manage the responses. First, we need to install dependencies
by running the command below:

yarn add axios-mock-adapter

Next, let’s create the mocks. If the request is successful, a response with a status code
of 200 will be received.

import MockAdapter from 'axios-mock-adapter';


import Faker from 'faker'
import ApiClient from '../constants/api-client';
import userDetails from 'jest/mockResponseObjects/user-objects';

let mockApi = new MockAdapter(ApiClient.getAxiosInstance());


let validAuthentication = {
name: Faker.internet.email(),
password: Faker.internet.password()

mockApi.onPost('requests').reply(config) => {
if (config.data === validAuthentication) {
return [200, userDetails];
}
return [400, 'Incorrect username and password'];
});

codemagic.io 57
The mock is ready, so let’s write a test for an external API request:

it('successful sign in with correct credentials', async () => {


await store.dispatch(authenticateUser('ikechifortune@gmail.com',
'password'));
expect(getActions()).toMatchSnapshot();

Here, we’re testing a successful login with the correct credentials, using the native
JavaScript async await. Meanwhile, the authenticateUser function from Jest
authenticates the request and makes sure it matches our earlier snapshots. Similarly,
we test for an unsuccessful login in case incorrect credentials are used, such as an
incorrect email address or password.

Run yarn test or npm test in your terminal, and wait until the tests finish.

We have covered the basics of API testing for React Native applications using Jest.
With Jest, testing React Native applications has never been easier, especially with
snapshots, which ensure that the UI remains consistent regardless of the global styles.
The test-automation frameworks covered here are widely accepted and used for React
Native apps.

codemagic.io 58
Conclusion

With React Native, in most cases, you get the best of both worlds: You expend less
effort and take advantage of lower associated costs and speedy time to market while
still producing a robust and performant app. We hope this ebook provides you with a
foundation for your journey of testing React Native apps.

Writing exhaustive test cases and test suites for mobile apps requires a holistic
approach, practice and an inclusive mindset to think of all the edge cases or scenarios
to cover. Writing test cases and test suites adds value and trust amongst the
development team members and helps the team to deliver robust apps confidently. We
strongly believe that the concepts discussed in this ebook will be useful, whether
you’re testing individual components via a unit testing approach or an entire app via a
functional testing approach. Thank you for choosing the approach of building robust
apps!

codemagic.io 59
About the Authors

This book was published in cooperation between Codemagic and Sneh Pandya.

Sneh is a Senior Product Manager based in Baroda. He is a community organizer at


Google Developers Group and co-host of NinjaTalks podcast. His passion for building
meaningful products inspires him to write blogs, speak at conferences and mentor
different talents. You can reach out to him over Twitter @SnehPandya18 or via email.

We are extremely thankful to Sneh for the cooperation!

codemagic.io 60
Thank you for reading that book!

Build, test and deliver mobile apps in record time with Codemagic CI/CD!

codemagic.io

codemagic.io 61

You might also like