Testing (Jest, Detox)
Testing React Native covers three layers: unit (Jest), component (React Native Testing Library), and end-to-end (Detox or Maestro). Each catches a different class of bug; together they cover what a manual smoke test would do without the manual part.
Jest + RNTL + Maestro examples
EXAMPLE
// 1) Setup — jest preset
// package.json
// {
// "jest": {
// "preset": "react-native",
// "setupFilesAfterEach": ["@testing-library/jest-native/extend-expect"]
// }
// }
// npm i -D jest @testing-library/react-native @testing-library/jest-native
// 2) Pure logic — unit test (Jest)
// src/lib/cents.ts
export function formatCents(cents: number, currency = 'AUD') {
return new Intl.NumberFormat('en-AU', { style: 'currency', currency }).format(cents / 100);
}
// src/lib/cents.test.ts
import { formatCents } from './cents';
describe('formatCents', () => {
it('rounds to two decimals', () => {
expect(formatCents(4995)).toBe('A$49.95');
});
it('handles other currencies', () => {
expect(formatCents(4995, 'USD')).toBe('USD49.95');
});
});
// 3) Component test — React Native Testing Library
// src/components/Counter.tsx
import { Text, Pressable, View } from 'react-native';
import { useState } from 'react';
export function Counter() {
const [n, setN] = useState(0);
return (
<View>
<Text testID='count'>Count: {n}</Text>
<Pressable accessibilityRole='button' onPress={() => setN((x) => x + 1)}>
<Text>Increment</Text>
</Pressable>
</View>
);
}
// src/components/Counter.test.tsx
import { render, fireEvent, screen } from '@testing-library/react-native';
import { Counter } from './Counter';
it('increments on press', () => {
render(<Counter />);
expect(screen.getByTestId('count')).toHaveTextContent('Count: 0');
fireEvent.press(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByTestId('count')).toHaveTextContent('Count: 1');
});
// 4) Mocking native modules
// __mocks__/react-native-async-storage-async-storage.js
// jest.mock('@react-native-async-storage/async-storage', () => ({
// getItem: jest.fn(() => Promise.resolve(null)),
// setItem: jest.fn(() => Promise.resolve()),
// }));
// 5) Snapshot a screen carefully — avoid brittle UI snapshots
import renderer from 'react-test-renderer';
it('matches snapshot', () => {
const tree = renderer.create(<Counter />).toJSON();
expect(tree).toMatchSnapshot();
});
// Treat snapshot failures as 'review the diff'; do not auto-update.
// 6) End-to-end with Maestro (recommended over Detox for new projects)
// flow.yaml
// appId: au.com.example.shop
// ---
// - launchApp
// - assertVisible: 'Welcome'
// - tapOn: 'Sign in'
// - inputText: 'alice@example.com'
// - tapOn: 'Continue'
// - inputText: 'hunter2'
// - tapOn: 'Sign in'
// - assertVisible: 'Catalog'
//
// Run on a real device or simulator:
// maestro test flow.yaml
// 7) End-to-end with Detox (heavier; tied to a built binary)
// detox.config.js → describes simulators / emulators
// e2e/sample.e2e.ts
// describe('Catalog', () => {
// it('shows products', async () => {
// await element(by.text('Catalog')).tap();
// await expect(element(by.text('Wool jacket'))).toBeVisible();
// });
// });
// 8) Test ID and accessibility
// - Prefer accessibilityRole / accessibilityLabel queries (testing accessibility for free)
// - Fall back to testID for opaque elements
// - Never query by text that is translated; use a stable key
// 9) Run in CI
// GitHub Actions matrix:
// - jest unit + component tests on every PR
// - Maestro on an emulator job for main branch deploys
// - Skip slow e2e in PRs; run a smaller smoke set instead
// 10) Pitfalls
// - Snapshot-heavy test suites: brittle, slow to update, low value
// - No mocks of network → tests hit production
// - Detox tests that depend on hard-coded device timing
// - Skipping accessibility queries → tests pass while screen-readers cannot use the app
Why it matters
Build the testing pyramid: many unit tests, fewer component tests, very few e2e tests. The unit tests catch logic bugs in milliseconds; component tests prove the UI wires up; e2e exists to confirm "the whole thing still works" on a real device once per deploy. Inverting the pyramid leads to slow, flaky CI that nobody trusts.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# Unit tests npm install -D jest @testing-library/react-native # End-to-end npm install -D detox detox testTry it Yourself »
Discussion
Loading…