Testing Best Practices
A condensed summary of the 25 most important best practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important best practices drawn from every page in this section.
Follow the testing pyramid: Many fast unit and component tests, fewer integration tests, minimal E2E - E2E belongs on critical paths only.
Use jest-expo as the preset: Aligns transforms and mocks with Expo SDK 57 - custom Jest config from web tutorials breaks on native imports.
Centralize setup in jest.setup.ts: Matchers, reanimated mocks, and NetInfo stubs live once - not copy-pasted per test file.
Fix transformIgnorePatterns when packages fail: Opt specific node_modules into transpilation - document the list when adding native-heavy deps.
Mock at service boundaries: Mock api/client, not every expo-* module - over-mocking hides integration bugs.
Avoid false confidence from empty mocks: jest.mock('expo-camera', () => ({})) lets tests pass while production crashes - return realistic shapes.
Query like users do: RNTL priority is role, label, then text - testID is escape hatch, not default.
Use userEvent over fireEvent: Press sequences match real interaction - fireEvent skips accessibility and timing paths.
Wait for async UI with findBy or waitFor: Assertions immediately after press flake on mobile - network and animations need async matchers.
Test presenters without providers when possible: Pure props-in JSX-out components run in milliseconds - reserve MSW for integration tests.
Snapshot only stable primitives: Buttons and badges, not entire screens - snapshot churn trains teams to click-through approve.
Colocate tests beside source: Button.test.tsx next to Button.tsx - distant __tests__ folders stop being updated.
Run tests in CI on every PR: npm test -- --ci with coverage optional - local-only testing does not scale past five engineers.
Separate unit and contract scripts: test:contract for Zod fixtures - fast signal without booting the full app.
Contract-test API responses with Zod: Shared schemas between client and tests - backend shape changes fail CI before QA.
Add Pact when multiple teams own APIs: Consumer-driven contracts complement schema tests - use when mobile and backend release independently.
Start Maestro with three smoke flows: Launch, sign-in, and primary tab - YAML flows are cheap to maintain vs Detox for many teams.
Pin appId in Maestro to bundle identifier: Wrong appId launches the wrong binary silently - match app.config ios.bundleIdentifier / android.package.
Run Maestro on EAS build artifacts in CI: Flows against release-like binaries - Expo Go is not a store submission stand-in.
Reach for Detox when sync is the bug: Gray-box synchronization helps gesture-heavy flows - accept native build setup cost.
Use Storybook for visual states, not navigation: Catalog loading/error/empty variants - on-device Storybook beats screenshotting production screens.
Gate Storybook behind STORYBOOK_ENABLED: Swap entry in dev only - production bundles must not ship story routes.
Flake policy: fix or quarantine: Quarantined E2E must have owner and expiry - retry loops hide product bugs.
Test offline and airplane mode paths: NetInfo mocks in unit tests; real device check before release - mobile users lose signal constantly.
Pre-submission checklist: Unit + component green, smoke Maestro on physical device, contract tests, and manual payment/permission flows once per RC.
renderRouter for integration tests on critical stacks.Stack versions: This page was written for React 19.2.3, React Native 0.86.0, and Expo SDK 57 (
expo~57.0.4).
Reviewed by Chris St. John·Last updated Jul 16, 2026