Updated August 21, 2019
React Native End to End Testing with Detox
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:
- Component Testing with React Native Testing Library
- Mocking Fetch API Calls When Using Jest
- Test Driven Development with AsyncStorage
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!
It may look like me tapping in the app but I assure you it's detox doing the work!
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.