Skip to main content
Version: Next

Your First Test

The previous articles have addressed the environment and project setup, and now it is time for writing and running the tests.

If you are eager to check first whether your build configuration was correct, you can skip writing a test for now and try running tests instead, to identify potential late issues caused by incorrect project configuration.

Writing a test

This subsection shows how to write a test which can:

  • launch the application,
  • tap on a button,
  • and assert that some text appears as a result.

Also, it will familiarize you with commonly used Detox actions, assertions and matchers along the way.

1. Create a test suite

Note

You can also duplicate and modify a e2e/starter.test.js file that was generated automatically by detox init command.

Create a new test file under your e2e folder and add a similar test suite skeleton:

e2e/yourTestName.test.js
describe('Example', () => {
beforeAll(async () => {});

beforeEach(async () => {});

it('should test something', async () => {});
});

2. Launch the application

When your test starts, the application is not running yet. You need to call device.launchApp() at least once, e.g. in the beforeAll hook:

describe('Example', () => {
beforeAll(async () => {
await device.launchApp();
});

// …
});

If your app supports deep links, you can configure it to start from a specific screen.

tip

It is a good idea to start every test from a fresh state, since the preceeding ones might leave your application in an unpredictable state if they fail.

One way to do it is to launch the app as a new instance in beforeEach hook instead:

beforeEach(async () => {
await device.launchApp({ newInstance: true });
});

The other way is to reload React Native without restarting the app. Like any live reloading, it is apt to cause glitches for more complex apps, but for simpler apps it proves to be a quicker way to reset the state between the tests:

beforeEach(async () => {
await device.reloadReactNative();
});

So, pick your favorite one wisely, on the basis of speed vs stability considerations.

3. Match an element

The next step is to match an element you want to interact with.

Detox provides many options to match an element by.id(), by.label(), by.text() and more. The most common way to match elements is either by id or text:

element(by.id('YourTestID')); // recommended
element(by.text('Element text'));
Best practice

Try to use by.id() matcher wherever possible. Explore our guide to learn how to work with testID props.

4. Perform an action

Detox provides many actions on elements such as tap(), swipe(), scroll() and other interactions.

Let's tap on an element for the sake of the example:

describe('Example', () => {
// …

it('should tap on a button', async () => {
await element(by.id('ButtonID')).tap();
});
});
Note

You don't need to wait or "sleep" after interacting with an element, because Detox already does it for you. It synchronises with your application and waits after each action for all the foreground activities to finish before performing any further step.

While convenient, this feature goes sideways at times, for example, when your app has looping animations causing Detox to wait forever. This is why you sometimes have to tweak your app specifically for Detox tests, and there is a special guide for that.

5. Make an assertion

An essential step of any test is making an assertion.

Detox provides its own expect object, so that you can expect from any element toExist(), toBeVisible(), toHaveText() and more various things. All the assertions can be inverted with not property.

Let's assert that our text is shown on a screen using toBeVisible() function:

describe('Example', () => {
beforeAll(async () => {
await device.launchApp();
});

beforeEach(async () => {
await device.reloadReactNative();
});

it('should tap on button by id and expect some text to be visible', async () => {
await element(by.id('ButtonID')).tap();
await expect(element(by.text('The button has been pressed'))).toBeVisible();
});
});

Note that instead of matching by text you can assert a specific text on an element with toHaveText(), e.g.:

await expect(element(by.id('TextAfterButtonPressed'))).toHaveText('Button pressed');

Running tests

Since this is a debug configuration, you need to have React Native packager running in parallel before you start Detox tests:

npm start

> react-native start

# #######
# ################
# ######### #########
# ######### ##########
# ######### ###### #########
# ##########################################
# ##### ##################### #####
# ##### ############## #####
# ##### ### ###### ### #####
# ##### ####### ####### #####
# ##### ########### ########### #####
# ##### ########################## #####
# ##### ########################## #####
# ##### ###################### ######
# ###### ############# #######
# ######### #### #########
# ######### #########
# ######### #########
# #########
#
#
# Welcome to Metro!
# Fast - Scalable - Integrated

Now you can run your first test:

detox test --configuration ios.sim.debug

If you haven't changed the generated e2e/starter.test.js, you are likely to see errors like this:

 FAIL  e2e/starter.test.js (25.916 s)
Example
✕ should have welcome screen (662 ms)
✕ should show hello screen after tap (236 ms)
✕ should show world screen after tap (236 ms)

● Example › should have welcome screen

Test Failed: No elements found for “MATCHER(id == “welcome”)”

HINT: To print view hierarchy on failed actions/matches, use log-level verbose or higher.

9 |
10 | it('should have welcome screen', async () => {
> 11 | await expect(element(by.id('welcome'))).toBeVisible();
| ^
12 | });
13 |
14 | it('should show hello screen after tap', async () => {

at Object.toBeVisible (e2e/starter.test.js:11:45)


If you have created your own test, and it is failing, examine the error message, check out our Investigating Failures and Debugging guides, and run your tests again after you fix the issue.