React Native E2E Testing With Detox

When you develop for web you have a lot of options to set up your e2e tests. Protractor, CasperJS, PhantomJS, DalekJS and a lot of others. That’s not the case in the world of mobile development. But worry not, I’m going to show you the best way (in my opinion) to test your application from users point of view.

I think everyone will agree that having bugs is lame. Testing your application manually is also lame: it requires a lot of time, and you can forget to test certain scenarios or just overlook bugs.

If only you could make robots do your job!

E2E Automation

test pyramid

Tests we are going to discuss here sit on top of the Martin Fowler’s Test Pyramid. They are the slowest and most expensive to support. Every time you change any part of your system – you might break one of those. So as a rule of thumb keep them low. Test only required scenarios, and don’t use them to test things that can be tested with unit tests.

But even though they are slow, expensive and brittle – it’s still much better than a meat-bag poking your app with his sausage fingers.

Real Example

I’m going to assume that you are using React Native, but AFAIK the setup and workflow won’t change much even if you write native apps.

Make sure you have Node v6 or later and XCode 8 or later installed. I recommend to use n to manage node versions. Also make sure to install yarn.

$ yarn global add create-react-native-app
$ create-react-native-app detox-e2e-tutorial
$ cd detox-e2e-tutorial

These commands will install the create-react-native-app script and create the detox-e2e-tutorial project.

Initially your newly created project is going to be run in Expo app. We want it to be truly native. So run the following command:

$ yarn eject

It will ask you a couple of questions, here is how I answered them:

? How would you like to eject from create-react-native-app? React Native: I'd like a regular React Native project.
We have a couple of questions to ask you about how you'd like to name your app:
? What should your app appear as on a user's home screen? Detox E2E Tutorial
? What should your Android Studio and Xcode projects be called? detoxe2etutorial

Let’s check that everything works:

$ yarn ios

It will start an app in simulator and also will run bundler in terminal (you can also run bundler manually with yarn start).

You should see:

Open up App.js to start working on your app!
Changes you make will automatically reload.
Shake your phone to open the developer menu.

Great, let’s modify our App.js so we’ll have something to test.

Prepare The App

Open the App.js file in the root of your project and remove everything. Copy and paste the following contents there:

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

export default class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      greeting: 'Welcome!'
    }
  }

  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.text}>
          {this.state.greeting}
        </Text>
        <TouchableOpacity testID='hello_button' onPress={this.onButtonPress.bind(this)}>
          <Text style={styles.button_text}>Say Hello</Text>
        </TouchableOpacity>
      </View>
    );
  }

  onButtonPress(greeting) {
    this.setState({
      greeting: 'Hello world!'
    });
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 25,
    marginBottom: 30
  },
  button_text: {
    color: 'blue',
    marginBottom: 20
  }
});

Open the app (run yarn ios). You should see text Welcome! and blue text Say Hello. Press that Say Hello thing. The top label should change text to Hello world!.

This is super simple usage scenario, but it’s enough for our purposes. Let’s automate it.

Setup Detox

Even though there are other options like Appium, I highly recommend Detox. Reasons are totally practical:

  • It’s faster.
  • It’s less flaky.
  • It’s platform agnostic (you can test both iOS and Android)
  • It works both with React Native and regular applications
  • it has great documentation.

Unlike other e2e solutions Detox uses gray box testing model. Which means that it has some knowledge about the system internals.

And this is exactly why it’s a lot more fast and reliable. I’m not going to go in-depth here, go and read the documentation page explaining how Detox is different.

Let’s continue with setup. Make sure you have Homebrew installed. Run:

$ brew tap wix/brew
$ brew install --HEAD applesimutils

This is Detox dependency that’s needed to control iPhone simulator.

Now install detox-cli to be able to run Detox commands.

$ yarn global add detox-cli

And install Detox for your project:

$ yarn add --dev detox

Install some test-runner. I’m going to use Jest:

$ yarn add --dev jest

Also add these lines to scripts block:

"test:e2e": "detox test -c ios.sim.debug",
"test:e2e:build": "detox build"

Now create e2e folder in your projects root and create file init.js with following contents:

const detox = require('detox');
const config = require('../package.json').detox;

// Set the default timeout
jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000;

beforeAll(async () => {
  await detox.init(config);
});

afterAll(async () => {
  await detox.cleanup();
});

Also make Detox to run with Jest, add config.json file to e2e folder:

{
  "setupTestFrameworkScriptFile": "./init.js"
}

And add the following block to the package.json:

"detox": {
  "test-runner": "jest",
  "runner-config": "e2e/config.json",
  "configurations": {
    "ios.sim.debug": {
      "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/detoxe2etutorial.app",
      "build": "xcodebuild -project ios/detoxe2etutorial.xcodeproj -scheme detoxe2etutorial -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
      "type": "ios.simulator",
      "name": "iPhone 7"
    }
  }
}

Phew, almost there. Let’s write our test.

First Test

First and the only. Our application is very simple so will be our test.

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

  it('allows to change label text to "Hello world!"', async () => {
    await expect(element(by.id('label'))).toHaveText('Welcome!');;
    await element(by.id('hello_button')).tap();
    await expect(element(by.id('label'))).toHaveText('Hello world!');;
  });
});

Run the test:

$ yarn test:e2e:build
$ yarn test:e2e

Congratulations

Now you can write user scenarios for your mobile applications. Go ahead and automate them all.

Here is Detox documentation index, you’ll find all the needed info including matchers and expectation there.