Detox is like having a virtual assistant that automatically tests your app by mimicking user interactions. It’s designed specifically for mobile apps. What’s cool about Detox is its “Grey Box” testing model.
Detox doesn’t rely on a Webdriver, a common testing tool. Instead, it communicates directly with the native layers of the mobile devices, like Android’s Espresso and iOS’s EarlGrey. This means Detox can interact with the app as a real user without any unnecessary delays.
What is Detox Testing?
Detox is a testing framework that allows you to test mobile applications by simulating user interactions and verifying application behavior on real devices and emulators. Detox provides a robust solution for ensuring React Native applications work as expected across various scenarios, environments, and devices.
With Detox, you can write automated tests to simulate various user scenarios. For example, you can create a test that mimics a user searching for nearby rides, selecting a destination, and booking a ride. Detox will validate that the available rides are displayed accurately, the destination is correctly set, and the booking process completes smoothly.
Understanding the Architecture of Detox Framework
Detox Server runs on the device or simulator where your website is being tested. Detox Server understands the internal structure and processes.
- When you want to run a test, tell your Detox Client what actions to perform on your website. It then communicates with the Detox Server, which interacts directly with your website’s different components and elements.
- They use a WebSocket connection to communicate between the Detox Client and Detox Server. They’re chatting back and forth, exchanging information and ensuring smooth coordination.
- The Detox Server executes the commands received from the Detox Client and reports back the results.
Collaboration between the Detox Client and Detox Server allows you to efficiently and accurately test your website. It makes sure your website functions flawlessly before sharing it with the world.
Features of Detox
Key Features of Detox are:
- Synchronous Testing: Unlike other testing frameworks that may be asynchronous, Detox runs tests synchronously, ensuring that each step (such as, tapping a button, loading data) is fully completed before the next step begins.
- Precise Synchronization: Detox automatically waits for animations, network requests, and other asynchronous events to finish before proceeding with the next action. This prevents flaky tests.
- Device Simulation: You can simulate real-world device conditions, such as poor network connectivity or different screen sizes, to ensure your app behaves well under various conditions.
- Mocking and Stubbing: Detox allows you to mock API calls, simulate different states, and stub responses, helping you isolate your app’s functionality from external services.
- Snapshot Testing: Detox also supports visual testing where you can take screenshots and compare them to baseline images to detect UI regressions.
Pros and Cons of Detox Testing
Here are the advantages and disadvantages of the Detox testing framework.
Pros | Cons |
---|---|
Cross-platform support: Works seamlessly on both iOS and Android. | Complex setup: Initial setup, especially for iOS, can be time-consuming and tricky. |
Automatic synchronization: Waits for app activity to settle before executing commands, reducing flaky tests. | Limited WebView support: Testing WebViews in hybrid apps can be challenging. |
CI/CD integration: Designed to run in Continuous Integration pipelines for automated testing. | Instrumentation overhead: Requires modifying the app for testing, which may increase build complexity. |
User interaction simulation: Allows realistic testing of user interactions like tapping, swiping, and typing. | Requires a test-specific build: Detox supports debug builds but still needs a dedicated test configuration, adding complexity and slowing iteration. |
Open-source: Free to use with active community support and maintenance by Wix. | Learning curve: Developers must learn Detox’s specific syntax and synchronization model. |
Reliable results: The synchronization engine ensures tests are stable and reliable. | Limited debugging tools: Debugging failed tests can be harder than visual testing tools. |
Supports real devices: This can be run on emulators and real devices. | Resource intensive: Running on emulators and simulators may require substantial computing resources. |
Customizable: Compatible with popular test runners like Jest or Mocha. | Not for visual testing: Detox is not ideal for pixel-perfect visual validation. |
Common Testing Types Supported by Detox
Detox supports the following types of testing:
- Functional Testing: Ensuring that the app behaves correctly by testing the user flows, UI elements, and interactions.
- Regression Testing: Verifying that new changes or updates do not break existing functionality.
- Performance Testing: Ensuring the app performs optimally, especially during navigation, data fetching, and complex UI transitions.
- Real Device Testing: Running tests on real devices to catch issues that may not appear on simulators or emulators. Detox can be seamlessly integrated with BrowserStack App Automate to run tests on real devices.
- UI Testing: Testing the visual appearance of your app to detect UI issues like layout shifts, broken animations, and screen rendering problems.
Steps for Setting up a Detox Testing Project
Step 1. Install the necessary prerequisites on your macOS machine. Ensure you have macOS High Sierra 10.13 or above, Xcode 10.1 or above, and Homebrew installed.
Step 2. You’ll also need Node 8.3.0 or above and Apple Simulator Utilities.
You can use the following commands to install them:
- Run brew update && brew install node to update Homebrew and install Node.
- Run brew tap wix/brew && brew install applesimutils to install Apple Simulator Utilities.
Step 3. Install the Detox CLI by running the following command:
- Run npm install -g detox-cli to install the Detox CLI globally.
Step 4. Create a new React Native project using the following command:
- Run npx react-native init MyDetoxProject to create a new React Native project. You can replace “MyDetoxProject” with the desired name of your project.
Step 5. Install Detox in your project:
- Run npm install detox –save-dev to install Detox as a development dependency in your project.
Step 6. Configure Detox in your project:
- Create a file called “detox.config.js” in the root directory of your project.
- Add the following code to the “detox.config.js” file:
module.exports = { configurations: { ios: { type: 'ios.simulator', binaryPath: 'path/to/your/app', build: 'path/to/your/xcodeproj', device: { type: 'iPhone 11' } } } }
Replace the “path/to/your/app” and “path/to/your/xcodeproj” with the actual paths to your app and Xcode project files. Also, specify the desired device type (e.g., ‘iPhone 11’).
Step 7. Write your first Detox test:
Create a file called “firstTest.spec.js” in your project’s “e2e” directory.
Add the following example test code to “firstTest.spec.js”:
describe('Example', () => { beforeEach(async () => { await device.reloadReactNative(); }); it('should have welcome screen', async () => { await expect(element(by.id('welcome'))).toBeVisible(); }); });
This is just an example test. You can customize it based on your specific requirements.
Step 8. Run your Detox test
To run your Detox test, use the following command:
Run detox test –configuration ios to run the Detox test on the iOS simulator.
Detox End-to-End Testing Tutorial
Write your first Detox test: You can write your first Detox test in a file called firstTest.spec.js in the e2e directory of your project.
Here’s an e2e Detox testing example:
describe('Example', () => { beforeEach(async () => { await device.reloadReactNative(); }); it('should have welcome screen', async () => { await expect(element(by.id('welcome'))).toBeVisible(); }); });
Integrate Detox into your development workflow: You can integrate Detox into your development workflow by adding Detox commands to your package.json file.
Here’s an example:
"scripts": { "test:e2e": "detox test --configuration ios", "test:e2e:build": "detox build --configuration ios" }
Sample Detox test
Here’s an example of a Detox test that interacts with a login screen:
describe('Login Screen', () => { beforeEach(async () => { await device.reloadReactNative(); }); it('should show error message on invalid login', async () => { await element(by.id('username')).typeText('invalid_username'); await element(by.id('password')).typeText('invalid_password'); await element(by.id('loginButton')).tap(); await expect(element(by.id('errorMessage'))).toBeVisible(); }); it('should navigate to home screen on valid login', async () => { await element(by.id('username')).typeText('valid_username'); await element(by.id('password')).typeText('valid_password'); await element(by.id('loginButton')).tap(); await expect(element(by.id('homeScreen'))).toBeVisible(); }); });
This test checks if the login screen shows an error message on invalid login and navigates to the home screen on valid login.
Advanced Use Cases of React Native Detox
Once you set up Detox, you’d want to use it to test applications. Here are a few advanced cases of React Native Detox.
1. Testing App Initialization Under Various Conditions with Detox
Detox allows you to test app initialization under various conditions to ensure stability across different scenarios. Below are key use cases with implementation examples.
- App Launch with Default Configuration
Scenario: Ensure the app initializes correctly with its default settings.
Implementation:
it('should launch the app with default settings', async () => {await device.launchApp({newInstance: true}); await expect(element(by.id('homeScreen'))).toBeVisible(); });
- App Launch with Specific Launch Arguments
Scenario: Test how the app behaves when launched with specific parameters (e.g., feature flags, debug modes).
Implementation:
it('should launch the app with a mock server enabled', async () => {await device.launchApp({newInstance: true, launchArgs: {mockServer: true}}); await expect(element(by.id('mockDataScreen'))).toBeVisible(); });
- Testing Under Network Conditions
Scenario: Simulate app initialization under different network scenarios.
Implementation: During testing, use a network simulation tool (e.g., Charles Proxy) or mock network responses. Pass network condition flags using launchArgs.
it('should handle initialization in offline mode', async () => { await device.launchApp({newInstance: true, launchArgs: {offlineMode: true}}); await expect(element(by.id('offlineMessage'))).toBeVisible();});
- First-Time User Experience
Scenario: Test the onboarding flow for first-time users.
Implementation:
it('should display onboarding for first-time users', async () => { await device.launchApp({newInstance: true, launchArgs: { isFirstLaunch: true}}); await expect(element(by.id('onboardingScreen'))).toBeVisible();});
2. Validate Deep Linking Functionality
Deep linking allows users to open specific app screens via external links.
Scenario: Test deep links to ensure the app handles them correctly.
Implementation: Use Detox’s device.launchApp() with the URL parameter to simulate deep links.
await device.launchApp({ url: 'myapp://details?id=123' }); await expect(element(by.id('detailsScreen'))).toBeVisible();
3. Testing Notifications and Push Interactions
Push notifications are crucial to engaging users, but how do you know they work properly? Detox enables testing how the app behaves when receiving and handling notifications.
Scenario: Test app behavior when receiving push notifications.
Implementation: Use Detox’s device.sendUserNotification() to simulate push notifications.
await device.sendUserNotification({ trigger: { type: 'push' }, payload: { aps: { alert: { title: 'New Message', body: 'You have received a new message!', }, }, }, });
4. Testing Multi-Device and Screen Orientation
Apps must handle multi-device environments and adapt to screen orientation changes. Detox helps validate these interactions.
Scenario: Test scenarios involving multiple devices (e.g., real-time chat or peer-to-peer).
Implementation:
- Launch the app on multiple devices using different configurations
- Use detox.config.js to define multiple devices and handle interactions between them
5. Keyboard and Input Field Testing
Testing interactions with input fields and the keyboard is a common and critical use case in mobile applications. Detox provides robust methods for simulating user interactions with input fields and handling the virtual keyboard.
Scenario: Typing and Verifying Input
Implementation:
it('should type and verify text in the input field', async () => { await element(by.id('usernameInput')).typeText('testUser'); await expect(element(by.id('usernameInput'))).toHaveText('testUser'); });
6. Authentication Flows
Authentication flows often involve multiple steps, such as login forms, multi-factor authentication (MFA), and error handling. Detox is well-suited to test these scenarios by simulating user interactions and verifying app states.
Scenario: Verifying login with valid credentials.
Implementation:
it('should log in with valid credentials', async () => { await element(by.id('emailInput')).typeText('user@example.com'); await element(by.id('passwordInput')).typeText('password123'); await element(by.id('loginButton')).tap(); await expect(element(by.id('homeScreen'))).toBeVisible(); });
7. Simulating Network and API Failure Scenarios
When testing a React Native application, it’s crucial to verify how the app behaves under different network conditions, including failures, timeouts, and slow responses.
Detox allows you to simulate these scenarios using network mocking, interceptors, and custom handlers.
Method to simulate API Failures in Detox –
Using a mock server:
A mock server like json-server, WireMock, or MirageJS helps intercept API requests and return controlled responses.
1. Setup a mock API server
- Install json server
npm install -g json-server
- Create a mockData.json file
{ "users": [ { "id": 1, "name": "John Doe" } ] }
- Start the mock server
json-server --watch mockData.json --port 3000
Modify Detox tests to use the Mock API:
Modify the beforeEach hook in Detox to inject a failure scenario.
beforeEach(async () => { await device.launchApp({ newInstance: true, launchArgs: { MOCK_API_URL: "http://localhost:3000" } }); });
Simulate Failure Responses:
Modify the mockData.json file to simulate different failures:
- Network Failures
{ "error": "Network request failed" }
- 500 Internal Server Error
{ "status": 500, "message": "Server is down" }
Writing Detox Tests for API Failures:
Example: Verify Network Error Handling
it("should display an error message when the API fails", async () => { await device.launchApp({ newInstance: true, launchArgs: { SIMULATE_FAILURE: true } }); await element(by.id("fetchDataButton")).tap(); // Expect error message to be displayed await expect(element(by.text("Network request failed"))).toBeVisible(); });
Read More: End-to-End React Native Testing
Detox Cheatsheet
This cheat sheet covers the essential commands and concepts for using Detox in React Native testing.
1. SetUp Commands
Setting up Detox requires initializing it in your project, building the app, and running tests. Below are the key commands for each step.
- Initialize Detox in a project
Use the below commands to initialize detox in your project –
detox init -r jest
-r jest : Use jest as the test runner
- Build the App
Commands for building your app –
detox build --configuration ios detox build --configuration android
- Run the tests
The command for running your detox tests –
detox test --configuration ios detox test --configuration android
- Debug mode
Command to debug your tests –
detox test --debug-synchronization 200
This helps identify async issues with a timeout of 200 ms.
- Rerun a specific test
Command for re-running a test –
detox test -f "test name"
2. Element Selectors
Detox provides multiple ways to select UI elements. Below are the different selector methods.
- By ID
Search an element by ID –
element(by.id('elementID'));
- By Text
Search an element by text –
element(by.text('Submit'));
- By Label (iOS only)
Search an element by label –
element(by.label('LabelName'));
- By Type
Search an element by type –
element(by.type('RCTTextField')); // React Native text input
3. Element Actions
Detox allows performing various actions on UI elements, such as tapping, typing, and scrolling.
- Tap an element
Command to perform tap operation on an element –
await element(by.id('submitButton')).tap();
- Long Press
Command to long press on an element –
await element(by.id('elementID')).longPress();
- Type Text
Command to send values to an element –
await element(by.id('inputField')).typeText('Hello');
- Clear Text
Command to clear existing text –
await element(by.id('inputField')).clearText();
- Replace Text
Command to replace an existing text with new text –
await element(by.id(‘inputField’)).replaceText(‘New text’);
- Scroll
Command to scroll window –
await element(by.id('scrollView')).scroll(100, 'down');
4. Assertions
Assertions help verify the expected behavior of UI elements in Detox tests. Here’s how to use it.
- Check Visibility
Command to validate the visibility of an element –
await expect(element(by.id('elementID'))).toBeVisible();
- Check Text
Command to validate a text –
await expect(element(by.id('inputField'))).toHaveText('Hello');
- Check Enabled/Disabled
Command to check whether an element is enabled or disabled –
await expect(element(by.id('submitButton'))).toBeEnabled(); await expect(element(by.id('submitButton'))).toBeDisabled();
5. Wait Commands
Detox provides commands to wait for elements or text to appear within a given timeframe. Here’s how to use it.
- Wait for an Element
Command to wait for an element for a specified time –
await waitFor(element(by.id('submitButton'))) .toBeVisible() .withTimeout(5000);
- Wait for Text
Command to wait for a text for a specified time –
await waitFor(element(by.text('Success'))) .toBeVisible() .whileElement(by.id('scrollView')) .scroll(50, 'down');
6. Keyboard Actions
Detox enables simulating interactions with the keyboard, such as typing, pressing the return key, and dismissing the keyboard.
- Type Text
Command to send values to an element using keyboard keys –
await element(by.id('inputField')).typeText('Detox is great');
- Tap Return Key
The tapReturnKey() command is used to simulate pressing the Enter key on the keyboard –
await element(by.id('inputField')).tapReturnKey();
- Dismiss keyboard
This command is generally used after typing text into an input field to hide the keyboard before proceeding with other UI interactions.
await device.dismissKeyboard();
7. Hooks
Hooks allow you to execute setup and teardown logic before or after each test.
- beforeEach
Executed before each test. Used for tasks like relaunching the app, resetting states, or initializing specific configurations.
beforeEach(async () => { await device.reloadReactNative(); // Reloads the app to ensure a clean state });
- afterEach
Executed after each test. Used for cleaning up test-specific configurations, logging, or capturing screenshots for failed tests.
afterEach(async () => { // Example: Take a screenshot after each test const testName = expect.getState().currentTestName; await device.takeScreenshot(`after-${testName}`); });
- beforeAll
Executed once before all tests. Typically used to initialize Detox and set up the test environment.
beforeAll(async () => { await detox.init(); });
- afterAll
Executed once after all tests. Used for cleanup, such as shutting down Detox.
afterAll(async () => { await detox.cleanup(); });
Common errors in end-to-end testing with Detox
Developers and QA testers often face these errors while running E2E testing with Detox.
1. Build Errors
Error: Build Failed
Sometimes, the build might fail due to incorrect paths or missing dependencies in the Detox configuration. To resolve this, ensure that binaryPath and build commands in detox.config.json are correct.
For iOS:
xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build
For Android:
detox build --configuration android.emu.debug
2. Runtime Errors
Error: App has not responded to network requests
This error is caused when detox waits for all network calls to finish, but the app is stuck or has long-running tasks. In this case, use the “waitFor command” to synchronize tests.
Error: Element Not Found
This error is caused when detox is unable to locate an element due to incorrect testID or the element not rendered. In this case, verify that an element exists in the component with a testID or add a delay if the element takes longer time to render.
await waitFor(element(by.id('myElement'))).toBeVisible().withTimeout(5000);
Error: cannot perform an action because the element is not visible
This error might occur when the element is outside the viewport or rendered. To avoid this issue, try to wait for the element to render or scroll to the element to ensure it is on screen.
await element(by.id('scrollView')).scrollTo('bottom');
3. Flaky Tests
Error: Test Failed Randomly
Flaky tests occur due to race conditions or asynchronous operations. To avoid this, use waitFor to ensure the elements are interactable before performing actions.
await waitFor(element(by.id('button'))).toBeVisible().withTimeout(5000);
4. Detox Synchronization issues
Error: Detox cannot synchronize with the app
This error occurs when detox cannot detect app idleness due to custom async operations or heavy background tasks. In this case, one can wrap the problematic areas with device.disableSynchronization() and device.enableSynchronization():
await device.disableSynchronization(); await someLongTask(); await device.enableSynchronization();
Read More: How to Debug React Code: Tools and Tips
Running Detox Tests on a Real Device Cloud
Running Detox tests on real devices using BrowserStack involves integrating your Detox setup with BrowserStack’s App Automate platform. This allows you to test your React Native app on various real devices hosted on BrowserStack.
Here are the steps to integrate Detox with BrowserStack:
1. Prerequisites
- A BrowserStack account
- Detox installed and configured in your project
- Your app build is ready for testing
2. Setup Browserstack Credentials
- Log in to your BrowserStack account.
- Navigate to App Automate.
- Copy your Username and Access Key from the dashboard.
- Set these as environment variables:
export BROWSERSTACK_USERNAME="your_username" export BROWSERSTACK_ACCESS_KEY="your_access_key"
3. Build the App
Prepare your app build for testing on BrowserStack.
For iOS:
detox build --configuration ios.sim.debug
For Android:
detox build --configuration android.emu.debug
4. Upload the App to BrowserStack
Upload the app binary (APK or IPA file) to BrowserStack using their API or CLI:
curl -u "BROWSERSTACK_USERNAME:BROWSERSTACK_ACCESS_KEY" \ -X POST "https://api-cloud.browserstack.com/app-automate/upload" \ -F "file=@path/to/your/app.apk"
This returns an app_url that you’ll use in your test configuration.
5. Modify Detox Configuration
Update detox.config.json to include the BrowserStack device configuration.
For example:
{ "testRunner": "jest", "runnerConfig": "e2e/config.json", "apps": { "ios.debug": { "type": "ios.app", "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/MyApp.app" }, "android.debug": { "type": "android.apk", "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk" } }, "devices": { "ios.real": { "type": "ios.real.device", "device": { "name": "iPhone 14", "os": "16" }, "server": "ws://localhost:8099" }, "android.real": { "type": "android.real.device", "device": { "name": "Samsung Galaxy S22", "os": "13" }, "server": "ws://localhost:8099" } }, "configurations": { "ios": { "device": "ios.real", "app": "ios.debug" }, "android": { "device": "android.real", "app": "android.debug" } } }
Replace the name and OS in the device object with a BrowserStack-supported device.
6. Update Jest Test Runner Configuration
Configures Jest to work with Detox for running end-to-end tests in your React Native application.
const detox = require('detox'); const config = require('../package.json').detox; beforeAll(async () => { await detox.init(config, { launchApp: false }); }); beforeEach(async () => { await device.launchApp({ newInstance: true }); }); afterAll(async () => { await detox.cleanup(); });
7. Run Tests on BrowserStack
Execute the tests using the Detox CLI with the BrowserStack configuration.
For iOS:
detox test --configuration ios --record-logs all --record-videos all
For Android:
detox test –configuration android –record-logs all –record-videos all
8. Debugging and Logs
BrowserStack automatically captures logs, screenshots, and videos of test runs. Navigate to App Automate Dashboard in BrowserStack to view detailed reports and device logs.
Conclusion
Detox is an end-to-end testing framework for React Native applications. It allows developers to perform UI testing, catch bugs early, streamline testing workflows, and maintain app stability across different environments.
While Detox excels at end-to-end testing for React Native apps, it is limited by the number of devices it supports and local setup requirements. Integrating it with BrowserStack expands testing capabilities by providing access to a wide range of real devices through the cloud. Its features, like parallel testing, debugging tools, and CI/CD integration, help you test your app more thoroughly in real user conditions.