Test Automation for React Native: Practical Guide (2026)
React Native straddles JavaScript and native. Testing it well means covering both sides — the JS logic (unit + integration tests) and the rendered UI (E2E tests that exercise the bridge). This guide c
React Native straddles JavaScript and native. Testing it well means covering both sides — the JS logic (unit + integration tests) and the rendered UI (E2E tests that exercise the bridge). This guide covers the 2026 recommended stack.
The test pyramid for RN
- Unit / component tests (Jest + React Native Testing Library) — majority
- Integration tests (Jest with mocked native modules)
- E2E tests (Detox or Appium or SUSA) — critical flows
Unit tests are fast, run in Node without Metro. E2E tests exercise a real app. Integration splits the difference.
Jest + React Native Testing Library
For component-level testing:
import { render, fireEvent } from '@testing-library/react-native';
test('button click submits', () => {
const onSubmit = jest.fn();
const { getByText } = render(<SubmitButton onPress={onSubmit} />);
fireEvent.press(getByText('Submit'));
expect(onSubmit).toHaveBeenCalled();
});
Run in watch mode during development. Run in CI on every PR. Fast feedback.
Mocking native modules
Native modules (camera, push, deep link handlers) must be mocked for Jest:
jest.mock('@react-native-firebase/messaging', () => ({
default: () => ({
requestPermission: jest.fn(() => Promise.resolve(1)),
getToken: jest.fn(() => Promise.resolve('fake-token')),
}),
}));
Global mocks in jest.setup.js for modules used everywhere.
Detox
Detox is the RN-native E2E testing framework. It is synchronized with the app's JS runtime — tests wait for network idle and animation completion automatically:
describe('Login', () => {
beforeEach(async () => { await device.reloadReactNative(); });
it('should log in with valid credentials', async () => {
await element(by.id('email')).typeText('test@example.com');
await element(by.id('password')).typeText('password');
await element(by.id('submit')).tap();
await expect(element(by.id('home'))).toBeVisible();
});
});
Pros:
- Fast (no sleep-based waits)
- Reliable
- RN-specific (knows about Metro, hot reload)
Cons:
- Android setup is still rough
- iOS only supports simulator (no real device)
- Framework locked to RN — not portable
Appium
Cross-platform E2E. Works for RN apps but not RN-aware:
def test_login(driver):
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "email").send_keys("test@example.com")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "password").send_keys("password")
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "submit").click()
# Manual wait
WebDriverWait(driver, 10).until(EC.presence_of_element_located((AppiumBy.ACCESSIBILITY_ID, "home")))
Pros:
- Runs on real devices (cloud farms)
- Same framework for Android / iOS
- Wide ecosystem
Cons:
- Slower than Detox
- Flakier (manual waits)
- No Metro synchronization
SUSA for RN
SUSA works with RN as with any other native app. It does not need to know about JS internals — it drives the OS-level rendered UI via ADB (Android) or Appium (iOS). Benefits:
- No need to write test scripts
- Auto-generates Appium scripts usable across CI
- Catches RN-specific crashes (bridge serialization, null derefs) via logcat monitoring
- Stress tests surface RN layout bugs (rapid reload + navigation)
susatest-agent test rnapp.apk --persona adversarial --steps 200
Accessibility testing for RN
RN accessibility:
accessibleprop on ViewaccessibilityLabelaccessibilityHintaccessibilityRoleaccessibilityState
Snapshot tests cover static semantics. Runtime tests require a screen reader and a human, or SUSA's accessibility_user persona.
Snapshot testing
React's snapshot testing for component trees:
expect(render(<UserCard user={user} />).toJSON()).toMatchSnapshot();
Catches accidental UI changes. Pitfall: snapshots become stale and auto-updated without review. Keep them small and reviewed.
Visual regression for RN
Storybook + Chromatic works well for component-level. Percy / Applitools work at E2E level. SUSA's cross-session visual diff covers the discovered UI surface.
Coverage
Track JS coverage (Jest --coverage). Aim for 70-80% on business logic. 100% is not the goal; meaningful coverage is.
Platform-specific code (iOS-only branches) often has lower coverage — acceptable if the platform-specific path is also tested end-to-end.
CI pipeline
name: RN CI
on: pull_request
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
- uses: codecov/codecov-action@v3
e2e-android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
script: npm run test:detox:android
e2e-ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- run: cd ios && pod install
- run: npm run test:detox:ios
Common RN bugs to test for
- Bridge serialization — passing non-serializable data
setStateafter unmount — "can't perform state update"- FlatList key warnings — missing keys cause re-render cascades
- Image OOM — large images in lists
- Keyboard avoidance — KeyboardAvoidingView behavior differs iOS vs Android
- Deep link into app from cold start
- Push notification deep link
Each deserves a dedicated test.
Fabric / New Architecture
If you are on Fabric (new RN architecture), test migration carefully. Component behavior can change subtly. Snapshot tests detect drift. Full E2E pass on both old and new architectures during migration phase.
RN testing is mature in 2026. A team shipping weekly can have 70% unit coverage, 15% integration, 15% E2E with flake rate under 2%. That is the target.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free