React Native End to End Testing with Detox

Author

Spencer Carli

Developer, cat dad, and devout pizza lover. Teaching at React Native School and building apps with Handlebar Labs.

Last Updated: August 21, 2019

In our continuation of testing month here at React Native School we're going to take one step further and cover End to End (E2E) testing. Previously we covered:

To accomplish this we'll be using the Detox library from Wix. This is a powerful library but I would be lying if I said it was easy to setup and manage. I've used it on multiple projects but not extensively - it was just too much to manage/maintain for me at this point.

That said, I feel testing month would be incomplete without covering it because it is the go-to tool for E2E testing.

Detox is physically going to boot up a simulator and click in your app (really fast). It's the most real testing can get.

Setup

Note: I've only used Detox on iOS before. It's supposed to work on Android as well but I haven't tried it.

Rather than spending a thousand words creating a basic React Native project I'm going to go ahead and assume you can do that. If not, you probably don't need to be reading this lesson yet. Get familiar with React Native and then come back to this in the future.

I have an example project that has detox tests and other included that you can reference.

All the app does is make a fetch request for a list of posts and then renders them. You can click on any post and then view that post in detail.

The project is available on Github. It's the same one I've been using for all my testing lessons this month.

The two files you should be familiar with are PostList.js and Post.js.

Detox has a comprehensive getting started guide that you'll need to reference as you go. Below are a few of the highlights.

After you've installed the CLI globally, you need to install detox to your project

Terminal

yarn add --dev detox

And it's configuration to package.json.

package.json

{
  // ...
  "detox": {
    "configurations": {
      "ios.sim.debug": {
        "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/CHANGE_THIS.app",
        "build": "xcodebuild -workspace ios/CHANGE_THIS.xcworkspace -scheme CHANGE_THIS -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
        "type": "ios.simulator",
        "name": "iPhone 7"
      }
    }
  }
}

NOTE: You need to replace CHANGE_THIS in the above snippet to your project name. More info can be found here.

Finally you initialize the default test and configuration.

Terminal

detox init -r jest

Doing this will create an e2e folder at the root of the project.

Writing a Detox Test

First, delete e2e/firstTest.spec.js and then create PostList.spec.js.

I keep all E2E tests in the root e2e/ folder so that I can easily filter them out when I want to run the tests that I run more often (jest . --testPathIgnorePatterns e2e/ node_modules/).

Since we're using Jest as the test runner much of this will look similar to what we've been covering this month. What detox is going to do is expose some additional globals to interact with our app.

First the scaffolding.

e2e/PostList.spec.js

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

  it('render a tappable list of posts', async () => {});
});

Before each detox test runs we want to reload React Native (the equivalent of pressing cmd + r) so that the next test is somewhat independent of the previous. I say somewhat because the app is physically installed and will have things saved in storage from the previous tests. Regardless, reloading before the next test is a best practice.

Now for the actual test. If you're a React Native School pro member than these tests may look very similar to what we wrote last week for integration tests. This time however the tests are physically running on a device.

e2e/PostList.spec.js

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

  it('render a tappable list of posts', async () => {
    await expect(element(by.id('post-list'))).toBeVisible();
    await waitFor(element(by.id('post-row-0')))
      .toBeVisible()
      .withTimeout(2000);
    await element(by.id('post-row-0')).tap();
    await expect(element(by.id('post-title'))).toBeVisible();
  });
});

element and by.id are coming to us from Detox. We can then use those results to interact/analyze our app.

First we check that our post-list component exists. We're then going to wait for a post row to exist. We're actually going to hit our API in this test. This is probably a good thing because we want to test the actual app (E2E tests are just there to replace your manual tapping through the app). Also, I can't figure out how to mock an API response in Detox so this is all we can do :).

Once that's visible we want to tap it, bringing us to the next screen. On that screen (Post.js) there should be a post-title and it should be visible. If all those conditions pass then our test passes!

Running Tests

First you'll want to build the app for detox by running detox build.

To actually run the app you'll run detox test.

This should boot a simulator and start tapping!

Test running

It may look like me tapping in the app but I assure you it's detox doing the work!

Test success

Summary

Detox can be a bit of a pain to setup and manage but it is very powerful. If you've got some common user flows that need to be tested thoroughly then you could replace yourself doing that before each release with a few convenient Detox tests.

Additional Resources

React Native by Example Logo

React Native by Example

Become an expert with React Native by building 10 completely unique apps. Nothing beats learning by doing.

Join the email list to be notified of all new lessons and classes!