Guide to Testing in React Native: End-to-End Test using Detox
Introduction
When I worked on this Guide to Testing article, I wanted to put all the different React native tests in one article. Just talking about Unit and Integration Tests in this part was long for a technical article.
So I will have another section to talk about End to End test in React Native.
End-to-end Testing
End-to-end (E2E) testing in React Native involves simulating real-user interactions with the application to verify its overall behavior. Unlike unit testing, which focuses on individual components in isolation, E2E testing aims to validate the application’s behavior from the user’s perspective, ensuring that it functions correctly throughout the user journey.
E2E testing is crucial for ensuring the overall quality and reliability of React Native applications. By simulating user interactions, E2E tests can identify and fix potential issues early on, such as bugs that arise from interactions between components or from interactions with external APIs. Additionally, E2E tests can help to ensure that the application is user-friendly and intuitive, as they can flag any usability issues or inconsistencies in the user interface
Type of End-to-End Testing:
End-to-end tests may be written and run in a wide variety of circumstances:
- They may be written by the developers working on the app, or by separate quality engineer/test automation professionals.
- They may be written while the feature functionality is being written, or after it’s complete.
- They may be run on simulators/emulators or physical devices.
- They may run on pull requests and block merge if any fail, or they may run on a separate schedule and tolerate some degree of random failures
Tools for end-to-end testing in React Native
There are several different options for end-to-end testing React Native apps that you might consider.
I will talk briefly about them, but focus more on Detox as it is my favorite, and will talk about how to use more in this article later
Appium
- Appium is a mobile test automation framework that can be used with any mobile application, including React Native. It is targeted at test automation engineers.
- Appium has several advantages, such as running on the same app binaries that are submitted to the App/Play Store, running on simulators/emulators and physical devices, and integrating with paid services like Sauce Labs that provide real physical devices of a wide variety of models available online for testing.
- However, Appium also has some disadvantages, such as experiencing some flake as a result of not having insight into the state of the running app, additional flake on services like Sauce Labs because of persistent state on simulators/emulators and physical devices, and a high learning curve for developers. Appium does not use technologies familiar to React Native developers.
Detox
Detox is a specialized testing framework designed explicitly for React Native, although it can extend its functionality to other Android and iOS applications. It prioritizes developer writability, seamless integration with React Native, and minimizing test flakiness.
Pros:
- React Native Integration: Specifically crafted to seamlessly integrate with React Native, aligning with the development workflow.
- Jest and JavaScript APIs: Utilizes Jest and JavaScript APIs familiar to React Native developers, simplifying the testing process for the development team.
- “Gray Box Testing”: Adopts a “gray box testing” approach, reducing flakiness by allowing Detox to autonomously wait for network operations, animations, and timers to complete.
Cons:
- Limited Device Integration: Detox has more limited integration with physical devices and test lab services like Sauce Labs compared to some other testing frameworks.
- CI Setup Challenges: Setting up Detox on Continuous Integration (CI) systems can be challenging, potentially requiring additional effort.
- Expo Compatibility: While Detox is not officially supported by the Expo ( community work to integrate expo), it can work with non-development clients if certain conditions are met.
Use Cases:
- Recommended for React Native Projects: Detox is recommended for React Native projects, especially when tests are authored by developers, run on pull requests, and are configured to block CI processes.
- Significant Native Integrations: Particularly beneficial for projects that involve significant native integrations within the React Native codebase.
- Expo Projects: For Expo projects, Detox may be suitable, but the testing feedback loop might be slower, leading to fewer tests for optimal efficiency.
React Native Testing Library (RNTL)
React Native Testing library can be used to test your entire application “end-to-end” in some cases. However, it does not support native code, so you cannot test navigation or other features that rely on native modules.
Pros of using RNTL for end-to-end testing:
- Compatible with Expo apps
- Faster than running native code in a real OS
- Less flaky than Appium and Detox tests
Cons of using RNTL for end-to-end testing:
- Does not integrate with native code, so you may need to mock out native code for certain tests
- Requires more manual handling of asynchrony
Manual Testing
Manual testing refers to a human using your app directly.
Pros:
- A human reviewing the app can notice subtle visual and animation issues such as drops in frame rate
- You get some amount of manual testing “for free” while trying out the app as you build it and in smoke testing before releasing a test build
Cons:
- Labor-intensive to manually retest your whole app on every PR
- Unless you’re a dedicated quality analyst, it’s hard to maintain the discipline to thoroughly test without accidentally missing steps or giving into the temptation to skip things that “shouldn’t break”
Use Detox in your App
As I already explained, we will use Detox for End-to-End testing to test our SearchInput Component explained before. let’s go
Installation
We will use Plain React native in Macos, if you have a different environment, check this link
//install detox cli
npm install detox-cli --global
// install applesimutils
brew tap wix/brew
brew install applesimutils
// install latest jest
npm install "jest@^29" --save-dev
// to install detox
npm install detox --save-dev
// for types of typescript
npm install --save-dev ts-jest @types/jest @types/node
after installation, run:
detox init -r jest
After Detox generated these files in your project’s root, you still have some work to do with them:
.detoxrc.js
– Detox config file;e2e/jest.config.js
– Jest configuration;e2e/starter.test.js
– dummy first test.
App configs
setting up detox is not that hard, but it takes time. Better to use this official website on how to configure your app to use Detox.
in the end, add these commands to your ‘’package.json’’ under scripts, so we can run the tests without the need to write long lines.
"android:clean": "cd android && ./gradlew clean && cd ..",
"e2e:build-ios-debug": "detox build -c ios.sim.debug",
"e2e:build-ios-release": "detox build -c ios.sim.release",
"e2e:test-ios-debug": "detox test -c ios.sim.debug",
"e2e:test-ios-release": "detox test -c ios.sim.release",
"e2e:build-android-debug": "yarn android:clean && detox build -c android.emu.debug",
"e2e:build-android-release": "yarn android:clean && detox build -c android.emu.release",
"e2e:test-android-debug": "detox test -c android.emu.debug",
"e2e:test-android-release": "detox test -c android.emu.release"
Write and run your e2e tests
First Remarks:
1- Add testID into your components:
- screens: the first element of the screen, should always be testID=’screen.<screenName>’
- components: in your components, testID should be testID=’<screenName>.<component Function in the screen >.<componentName>. for example, a button that submits when filtered in the search Input screen. testID=’SearchInputScreen.submitFilterValue.button’
2- in your ‘e2e/jest.config.js’ file, it should look like this, ( should include ts-jest for typescript)
module.exports = {
preset: 'ts-jest',
rootDir: '..',
testRegex: "\\.e2e\\.ts$",
testTimeout: 120000,
maxWorkers: 1,
globalSetup: 'detox/runners/jest/globalSetup',
globalTeardown: 'detox/runners/jest/globalTeardown',
reporters: ['detox/runners/jest/reporter'],
testEnvironment: 'detox/runners/jest/testEnvironment',
verbose: true,
};
3- your tests should be something <screenName>.e2e.ts
4- Create a folder called utils, where you put the functions that you always reuse, for example
//isScreenShowed.ts
import {by, element, expect} from 'detox';
// to check that the screen with screenId is visible, used for //navigation
export const isScreenShowed = async (screenId: string) => {
const myScreen = element(by.id(`screen.${screenId}`));
await expect(myScreen).toBeVisible();
};
5- Mock your data inside the utils folder, and put all that you need there:
//mockedData.ts
const USER_LOGIN={
"email": "malik_chohra_e2e@gmail.com",
"password":"123456789"
}
Write a Test for our App
Let’s Start writing our Tests for the Search Input component for property filtering
// searchInput.e2e.ts
const FILTER_BUTTON_TEST_ID = 'SearchInputScreen.submitFilterValue.button';
const SEARCH_INPUT_TEST_ID = 'SearchInputScreen.searchInput.input';
//Describe the E2e test
describe('Search Input Component', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should filter properties based on input (positive case)', async () => {
await element(by.id(SEARCH_INPUT_TEST_ID)).typeText('Office');
await element(by.id(FILTER_BUTTON_TEST_ID)).tap();
// at least one elements has the word office on it
await expect(element(by.text('Office'))).toBeVisible();
});
it('should handle empty input', async () => {
await element(by.id(FILTER_BUTTON_TEST_ID)).tap(); // Tap filter without entering any text
// Expect all items to be visible since there is no filter
await expect(element(by.text('Home'))).toBeVisible();
await expect(element(by.text('Office'))).toBeVisible();
await expect(element(by.text('Building'))).toBeVisible();
});
it('should handle non-matching input', async () => {
await element(by.id(SEARCH_INPUT_TEST_ID)).typeText('Nonexistent');
await element(by.id(FILTER_BUTTON_TEST_ID)).tap();
// Expect all items to be not visible since there are no matches
await expect(element(by.text('Home'))).toBeNotVisible();
await expect(element(by.text('Office'))).toBeNotVisible();
});
it('should handle case-insensitive filtering', async () => {
await element(by.id(SEARCH_INPUT_TEST_ID)).typeText('OFFICE');
await element(by.id(FILTER_BUTTON_TEST_ID)).tap();
// Expect 'Office' to be visible despite the case difference
await expect(element(by.text('Office'))).toBeVisible();
});
it('should handle multiple matches', async () => {
await element(by.id(SEARCH_INPUT_TEST_ID)).typeText('o');
await element(by.id(FILTER_BUTTON_TEST_ID)).tap();
// Expect both 'Home' and 'Office' to be visible
await expect(element(by.text('Home'))).toBeVisible();
await expect(element(by.text('Office'))).toBeVisible();
});
});
And now you build and test your app. for your iOS:
yarn e2e:build-ios-debug && yarn e2e:test-ios-debug
and Voila :D, you can see your devices running.
Detox cheat sheet
Detox Matchers:
Matchers are used to interact with elements in the app.
by.text('Hello')
: Selects an element by its visible text content.by.id('submitBtn')
: Selects an element by its testID.
Interact with Elements:
//Types text into a TextInput
element(by.id('usernameInput')).typeText('user123')
//Taps a Button
element(by.id('loginButton')).tap()
//Taps an element with specific text.
element(by.text('Logout')).tap()
Assertions:
//element by id to be visible
await expect(element(by.id('welcomeMessage'))).toBeVisible();
//element by id to be not visible
await expect(element(by.text('Error'))).toBeNotVisible();
Hooks:
beforeEach
: Executes before each test.afterEach
: Executes after each test.
beforeEach(async () => { await device.reloadReactNative(); });
Common Scenarios:
- Wait for an Element:
await waitFor(element(by.id('resultText'))) .toBeVisible() .withTimeout(5000);
- Scroll to Element:
await element(by.id('scrollButton')).scrollTo('bottom');
- Handling Alerts:
await device.alertHandler('OK', 'Dismiss');
Troubleshooting:
- Debugging Tests:
detox test -l verbose
- Inspecting UI Hierarchy:
detox trace -l debug
Advanced Topics:
- Simulating Network Conditions:
detox test --network-logs
- Performance Testing:
detox test --record-performance
Detox Tests Troubleshooting Summary:
Detox, like any testing tool, may encounter common issues during test execution. Addressing these challenges effectively is crucial for maintaining a streamlined testing process.
Configuration Errors:
Issue: Configuration errors often arise from incorrect setup or missing dependencies, affecting the PATH variable or configuration file properties.
Solution: Carefully review and follow Detox documentation, ensuring accurate execution of all setup steps. Verify the installation of required dependencies and validate the correctness of the configuration file.
Flaky Tests:
Issue: Flaky tests intermittently pass or fail, posing challenges in test result consistency.
Solution: Revise test cases to ensure determinism, making them produce the same outcome with identical initial conditions. Eliminate dependencies on specific states or data that may vary. Properly handle asynchronous operations, ensuring their completion before advancing in the test.
Timeout Errors:
Issue: Timeout errors occur when test operations exceed the allowed time, often due to slow application performance or delayed appearance of UI elements.
Solution: Adjust the test strategy by extending timeouts for naturally longer operations. If slow application load times are the cause, optimize the application for faster loading. Implement measures such as lazy loading of resources, database query optimization, or other performance enhancements.
check more of the issues in GitHub Detox if you have any problems.
And now that is it all with Detox for React Native, keep learning and keep growing.
As Alan Page put it:
“If we want to be serious about quality, it is time to get tired of finding bugs and start preventing their happening in the first place.”
========
For any more inquiries, don’t hesitate to DM me on Linkedin, all feedback and Improvements are welcome.