Time to detox

Writing e2e tests with React Native

Wojtek Szafraniec
Callstack Engineers
4 min readOct 5, 2017

--

Testing with React Native is something that I was always interested in. Being responsible for releasing several apps in our office, I follow a set of repetitive instructions to make sure that app is not going to break after upgrading. It’s good to look into ways to automate this process, so it is easier, faster and less error-prone.

To this day, there was never really a dedicated framework created with React Native in mind. There were native snapshots that Mike Grabowski blogged about last year, jest snapshots from Ferran Negre, cavy and appium e2e tests. Native snapshots and jest snapshots have been utilised in React Native core repository, adding precious value and confidence into the release pipeline.

However, none of them allowed me to turn on RNTester (react native official example app), click around and make sure we didn’t break Camera API or the bridge.

Detox is a brand new end-to-end test and automation library for React Native. Behind the scenes for testing is uses native solutions, Espresso for Android and EarlGrey for iOS. It runs your app on a simulator and gives you complete freedom to perform assertions on a real interface and interact with it by simulating arbitrary events. What’s great in Detox that it is a promise library so you don’t really care about wait(int) / timeouts like in Appium during e2e test.

Few months ago I was facing “timeout problem” when mobile app was working with some external printers whose printing time was depending on bluetooth / internet / usb connection. It was almost impossible to estimate printing time for one label when stretch test was about 1000 labels to print and if one of the printings took longer the test was broken. With promises I don’t need to specify time for action and test will not fail when connection breaks for a sec.

Cavy is an alternative that appeared recently as well, but the fact that it depends on refs made me advocate for Detox, since it presents a nicer API. Cavy also tests only your JavaScript part of code, not native one like Detox. Wix library also seems to be much faster while it’s working on web sockets. These things convinced me to give it a go!

It integrates well with your existing testing framework and it essentially boils down to the following:

describe('Main', () => {
it('should have welcome heading', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
});

Don’t worry if it looks cryptic at first. We will get to it at the end of this article.

The official documentation suggests using Mocha. In this example, I’ll show you how to set it up with Jest, which comes with every React Native app by default.

Getting started

The quickest way to get started is to follow official documentation (Step 1 and Step 2). We are going to skip Step 3 as its tightly coupled to Mocha. The installation instructions look complex, but in reality, it turned out to be a really smooth process.

We will start by adding Jest to our dependencies:

yarn add jest --dev

In my case, I have created 3 folders:

  • jest that contains setup files
  • __e2e__ that contains our Detox tests
  • __tests__ that contains other Jest tests

Now, in order to use Detox for all tests inside __e2e__ folder, we need to integrate it with Jest. We can do it in every test file, but that would be a bit cumbersome.

What we can do instead is to set the following in package.json:

"jest": {
"preset": "react-native",
"bail": true,
"setupTestFrameworkScriptFile": "<rootDir>/jest/setup.js"
}

Here, setupTestFrameworkScriptFile can be used to configure testing framework right before the test suite is going to be run. It’s a nice alternative to repeating config in every file.

The following setup.js file is straight copy from official documentation with an exception that we use Jest API instead of Mocha one:

const detox = require('detox');
const config = require('../package.json').detox;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000;/**
* If we are running `__e2e__` tests, we want to setup and configure
* detox.
*/
if (process.argv[2].includes('__e2e__')) {
beforeAll(async () => {
await detox.init(config);
});
beforeEach(async () => {
await device.reloadReactNative();
});
afterAll(async () => {
await detox.cleanup();
});
}

Finally, as a convenience, I encourage you to add the following npm scripts to your package.json:

{
"test-e2e": "detox build && jest __e2e__",
"test-unit": "jest __tests__",
"test": "yarn test-unit && yarn test-e2e",
}

And that’s it! You are all set!

You can browse the full example in Mike’s repository.

Writing tests

Before interacting with your components, you have to give them a name so that the testing framework can interact with it. This can be done by setting a special testID prop on any built-in React Native component.

Both platforms in native implementations has setter for testID

In Android:

In iOS:

For example, let’s make sure that default React Native app has a Welcome to React Native heading.

To do so, we first are going to open index.ios.js and set testID to welcome:

<Text testID="welcome" style={styles.welcome}>
Welcome to React Native!
</Text>

Now, lets create a __e2e__/welcome.spec.js file and make sure the content is visible:

describe('Main', () => {
it('should have welcome heading', async () => {
await expect(element(by.id('welcome'))).toBeVisible();
});
});

And that’s it! You can now run:

yarn test-e2e

To see the results of your work!

Testing with detox

You can browse the rest of Detox API to explore different matchers and expectations.

I wrote article together with Mike, so big shout out to Grabbou 🎉🎉🎉

Final repo, here

Thanks for your time, Wojtek.

--

--