E2E Testing with Detox
Implement end-to-end testing for React Native applications using Detox testing framework. This is a foundational concept in cross-platform mobile development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React Native experience. Take your time with each section and practice the examples
Detox Setup
Set up Detox for E2E testing with proper configuration for both iOS and Android platforms.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Detox Configuration
- Install Detox CLI and dependencies — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Configure detox.config.js for iOS and Android — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Set up test environment and device configurations — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Configure app builds for testing — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Set up test data and mock servers — a critical concept in cross-platform mobile development that you will use frequently in real projects
- Configure CI/CD integration — a critical concept in cross-platform mobile development that you will use frequently in real projects
E2E Test Scenarios
Write comprehensive E2E tests that cover complete user journeys and critical app functionality.. This is an essential concept that every React Native developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
comprehensive E2E Test Example
// e2e/firstTest.e2e.js
import { device, expect, element, by, waitFor } from 'detox';
describe('React Native App E2E Tests', () => {
beforeAll(async () => {
await device.launchApp();
});
beforeEach(async () => {
await device.reloadReactNative();
});
describe('Authentication Flow', () => {
it('should show login screen on app launch', async () => {
await expect(element(by.id('login-screen'))).toBeVisible();
await expect(element(by.id('email-input'))).toBeVisible();
await expect(element(by.id('password-input'))).toBeVisible();
await expect(element(by.id('login-button'))).toBeVisible();
});
it('should validate login form', async () => {
// Test empty form validation
await element(by.id('login-button')).tap();
await expect(element(by.text('Email is required'))).toBeVisible();
await expect(element(by.text('Password is required'))).toBeVisible();
// Test invalid email format
await element(by.id('email-input')).typeText('invalid-email');
await element(by.id('password-input')).typeText('password123');
await element(by.id('login-button')).tap();
await expect(element(by.text('Invalid email format'))).toBeVisible();
});
it('should successfully login with valid credentials', async () => {
await element(by.id('email-input')).clearText();
await element(by.id('email-input')).typeText('test@example.com');
await element(by.id('password-input')).clearText();
await element(by.id('password-input')).typeText('password123');
await element(by.id('login-button')).tap();
// Wait for navigation to home screen
await waitFor(element(by.id('home-screen')))
.toBeVisible()
.withTimeout(5000);
await expect(element(by.id('welcome-text'))).toBeVisible();
await expect(element(by.text('Welcome, test@example.com'))).toBeVisible();
});
});
describe('Navigation Flow', () => {
beforeEach(async () => {
// Login first
await element(by.id('email-input')).typeText('test@example.com');
await element(by.id('password-input')).typeText('password123');
await element(by.id('login-button')).tap();
await waitFor(element(by.id('home-screen'))).toBeVisible().withTimeout(5000);
});
it('should navigate to profile screen', async () => {
await element(by.id('profile-tab')).tap();
await expect(element(by.id('profile-screen'))).toBeVisible();
await expect(element(by.id('user-name'))).toBeVisible();
await expect(element(by.id('user-email'))).toBeVisible();
});
it('should navigate to settings screen', async () => {
await element(by.id('settings-tab')).tap();
await expect(element(by.id('settings-screen'))).toBeVisible();
await expect(element(by.id('theme-toggle'))).toBeVisible();
await expect(element(by.id('notifications-toggle'))).toBeVisible();
});
});
describe('User Profile Management', () => {
beforeEach(async () => {
// Login and navigate to profile
await element(by.id('email-input')).typeText('test@example.com');
await element(by.id('password-input')).typeText('password123');
await element(by.id('login-button')).tap();
await waitFor(element(by.id('home-screen'))).toBeVisible().withTimeout(5000);
await element(by.id('profile-tab')).tap();
});
it('should display current user information', async () => {
await expect(element(by.id('user-name'))).toHaveText('John Doe');
await expect(element(by.id('user-email'))).toHaveText('test@example.com');
await expect(element(by.id('user-phone'))).toHaveText('123-456-7890');
});
it('should edit user profile', async () => {
await element(by.id('edit-profile-button')).tap();
// Update name
await element(by.id('name-input')).clearText();
await element(by.id('name-input')).typeText('Jane Smith');
// Update phone
await element(by.id('phone-input')).clearText();
await element(by.id('phone-input')).typeText('987-654-3210');
await element(by.id('save-button')).tap();
// Verify changes
await expect(element(by.text('Profile updated successfully'))).toBeVisible();
await expect(element(by.id('user-name'))).toHaveText('Jane Smith');
await expect(element(by.id('user-phone'))).toHaveText('987-654-3210');
});
});
describe('Error Handling', () => {
it('should handle network errors gracefully', async () => {
// Simulate network error by using invalid credentials
await element(by.id('email-input')).typeText('invalid@example.com');
await element(by.id('password-input')).typeText('wrongpassword');
await element(by.id('login-button')).tap();
await expect(element(by.text('Invalid credentials'))).toBeVisible();
});
});
});
// detox.config.js
module.exports = {
testRunner: 'jest',
runnerConfig: 'e2e/config.json',
configurations: {
'ios.sim.debug': {
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/YourApp.app',
build: 'xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
type: 'ios.simulator',
device: {
type: 'iPhone 14',
},
},
'android.emu.debug': {
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
type: 'android.emulator',
device: {
avdName: 'Pixel_4_API_30',
},
},
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/Build/Products/Debug-iphonesimulator/YourApp.app',
build: 'xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build',
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
},
},
devices: {
simulator: {
type: 'ios.simulator',
device: {
type: 'iPhone 14',
},
},
attached: {
type: 'android.attached',
device: {
adbName: '.*',
},
},
},
artifacts: {
rootDir: './e2e/artifacts',
pathBuilder: './e2e/artifacts/pathBuilder.js',
screenshot: 'manual',
video: 'manual',
instruments: 'off',
},
behavior: {
init: {
reinstallApp: true,
},
cleanup: {
shutdownDevice: false,
},
},
session: {
server: 'ws://localhost:8099',
sessionId: 'detox',
},
};