Setting up Gray-box E2E testing with React Native using Detox

Iki
5 min readSep 4, 2021

Detox is an end to end testing library for React Native app that enable to interact just like real user, and test based on what we write on the script. In short, automation testing.

In this post, I will cover all required setup from the scratch in a React Native project for Android.

CREATE NEW PROJECT

I’ve assumed you already know React Native before, especially about environment and emulator setup. So, I will skip those steps.

In this demo, we will be using react-native cli. Run these commands to create and run a new React Native project.

$ npx react-native init demo-detox
$ cd wixDetox
$ react-native run-android

Now that our app is already prepared, let’s get started.

DETOX INSTALLATION

First, we need to install detox-cli (global)

$ yarn global add detox-cli / npm install -g detox-cli

Then, we also need to install detox package in our project (will using yarn from this point)

$ yarn add detox

Detox need to work with Test Runner. In this case, we will be using Jest (it’s a very popular platform for testing and also recommended by them). In their guide, it’s said that we need to install both jest and jest-circus (detail here).

$ yarn add jest jest-circus

After we finished Test Runner installation, we need to create the default config. We only need to run this command.

$ npx detox init -r jest

If it’s successful, these files will be added to our project:

  1. .detoxrc.js
  2. .detoxrc.json
  3. .detoxrc
  4. detox.config.js
  5. detox.config.json
  6. package.json ("detox" section)

Let’s move to the integration.

DETOX INTEGRATION

Now we need to setup the configuration on detoxrc.json

"configurations": {
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "cd android ; ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug ; cd -",
"type": "android.emulator",
"device": {
"avdName": "YOUR_EMULATOR_NAME"
}
},
"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": "YOUR_EMULATOR_NAME"
}
}
}

To find out your device/emulator name, you can just simply run this command:

$ emulator -list-avds

In my case, the code looks like this:

IMPLEMENT DETOX ANDROID DEPEDENCY

Now we need to register both google() and detox package in android/build.gradle

allprojects {
repositories {
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}

maven {
// All of Detox' artifacts are provided via the npm module
url "$rootDir/../node_modules/detox/Detox-android"
} /* here */
google() /* and here */ jcenter()
maven { url 'https://www.jitpack.io' }
}
}

We also need to update minSdkVersion to 21 and add Kotlin (should the latest version).

buildscript {
ext {
buildToolsVersion = "28.0.3"
ext.kotlinVersion = "1.3.70" /* should be the latest version */
minSdkVersion = 21 /* update minSdkVersion here */
compileSdkVersion = 28
targetSdkVersion = 28
}
repositories {
google()
jcenter()
}

dependencies {
classpath("com.android.tools.build:gradle:3.5.2")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" /* kotlin dependency */
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}

Then, we need to add another dependency. Open up android/app/build.gradle file and update the dependencies section like the code below:

dependencies {
//...
//...
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
} else {
implementation jscFlavor
}
androidTestImplementation('com.wix:detox:+') /* here */
}

We also need add an additional config, at defaultConfig subsection in the same file

defaultConfig {
applicationId "com.demorntester"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testBuildType System.getProperty('testBuildType', 'debug') /* here */
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' /* and here */
}

Then, we need to add the file android/app/src/androidTest/java/com/reactnativedetox/DetoxTest.java with the like code below:

package com.reactnativedetox;import com.wix.detox.Detox;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
// Replace 'MainActivity' with the value of android:name entry in
// <activity> in AndroidManifest.xml
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false);
@Test
public void runDetoxTests() {
Detox.DetoxIdlePolicyConfig idlePolicyConfig = new Detox.DetoxIdlePolicyConfig();
idlePolicyConfig.masterTimeoutSec = 60;
idlePolicyConfig.idleResourceTimeoutSec = 30;
Detox.runTests(mActivityRule, idlePolicyConfig);
}
}

UPDATE SCRIPTS

Detox has already provide some default tests in the e2e/firstTest.spec.js file. But the tests will fail, since in React Native default app, it doesn't provide testID that written in firstTest.spec yet. So, we have to manually set it up ourselves in App.js. But don't worry, you can just copy the App.js file with the code below (or here in GitHub file).

import React, {useState} from 'react';
import {Text, View, TouchableOpacity, StyleSheet} from 'react-native';
const App = () => {
const [displayedText, setDisplayedText] = useState(undefined);
const OutputText = () => (
<View
style={{
flex: 1,
paddingTop: 20,
justifyContent: 'center',
alignItems: 'center',
}}>
<Text style={{fontSize: 25}}>{displayedText}!!!</Text>
</View>
);
if (displayedText) {
return <OutputText />;
}
return (
<View testID="welcome" style={styles.root}>
<TouchableOpacity
style={styles.button}
testID="hello_button"
onPress={() => setDisplayedText('Hello')}>
<Text style={styles.buttonText}>Tap to display Hello!</Text>
</TouchableOpacity>
<TouchableOpacity
style={{...styles.button, backgroundColor: '#b71540'}}
testID="world_button"
onPress={() => setDisplayedText('World')}>
<Text style={styles.buttonText}>Tap to display World!</Text>
</TouchableOpacity>
</View>
);
};
export default App;const styles = StyleSheet.create({
root: {
flex: 1,
paddingTop: 20,
justifyContent: 'center',
alignItems: 'center',
},
button: {
width: 350,
height: 80,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#006266',
marginBottom: 32,
},
buttonText: {
fontSize: 24,
color: 'white',
fontWeight: 'bold',
padding: 10,
},
});

One more, we need to add additional scripts in package.json file to run the detox tests.

Open package.json file and add the code below

"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint .",
"build:android-debug": "detox build --configuration android.emu.debug", /* this */
"test:android-debug": "detox test --configuration android.emu.debug", /* this */
"e2e:android-debug": "yarn build:android-debug && yarn test:android-debug" /* and this */
},

Then in terminal, run this command to start the tests

$ yarn e2e:android-debug

TESTING & RESULTS

Done! Detox provides a lot more other functionality like typing in TextInput, screen navigation, etc. Explore more about Detox in their documentation here.

--

--

Iki

Software Engineer | Interests: self-growth, tech, health, and neuroscience