What you'll learn
- Write component and e2e tests
- Apply the testing pyramid: many unit tests, solid component tests, a few e2e flows
- Query elements by role and text the way a user finds them
- Cover loading, empty and error states and pin fixed bugs with tests
Write unit and component tests with Jest and React Native Testing Library, and an e2e flow.
Why it matters
Mobile releases are slow to undo. A web bug can be fixed in minutes; a broken mobile build may sit in users' hands until store review approves the fix and every user updates. Automated tests catch regressions before that happens and let a team change code with confidence. Mid-level mobile developers are expected to write them as a normal part of every feature.
The testing pyramid for mobile
- Unit testsFast tests of plain functions and reducers, run with Jest. (many, fast): plain functions and reducers. Price rules, validation, the navigation and cache logic you wrote earlier. Run with Jest.
- Component testsTests that render one component or screen in a simulated environment and interact with it like a user. (a good number): render one component or screen in a simulated environment and interact with it like a user. Use React Native Testing LibraryA library used with Jest to render components and query them by role and text. (RNTL) with Jest.
- End-to-end (e2e) testsSlow tests that drive the real app on a simulator, emulator or device through critical journeys. (a few, slow): drive the real app on a simulator, emulator or device, for the journeys that must never break (sign in, checkout). Use MaestroAn e2e testing tool whose flows are written in YAML and drive the app through the accessibility tree. (flows written in YAML) or Detox (tests written in JavaScript, synchronised with the app).
- Manual testing on real devices for what automation misses: feel, gestures, small screens, slow phones.
Setting up Jest in Expo
npx expo install jest-expo jest @testing-library/react-native --devAdd to package.json:
{
"scripts": { "test": "jest --watchAll=false" },
"jest": { "preset": "jest-expo" }
}The jest-expo preset mocks native modules so components render in Node without a phone.
Worked example: a component test
The component:
export function Counter({ initial = 0 }) {
const [count, setCount] = useState(initial);
return (
<View>
<Text>Count: {count}</Text>
<Pressable accessibilityRole="button" onPress={() => setCount(count + 1)}>
<Text>Add one</Text>
</Pressable>
</View>
);
}The test, written the way a user would interact:
import { render, screen, fireEvent } from "@testing-library/react-native";
import { Counter } from "./Counter";
test("adds one when the button is pressed", () => {
render(<Counter initial={2} />);
fireEvent.press(screen.getByRole("button", { name: "Add one" }));
expect(screen.getByText("Count: 3")).toBeOnTheScreen();
});The query getByRole("button", { name: ... }) finds the element by its accessibility role and label, so the test also checks that the button is accessible. Prefer role and text queries over test ids; reach for testID only when nothing user-visible identifies an element. Recent versions of RNTL include the toBeOnTheScreen matcher; older setups need @testing-library/jest-native.
For screens that fetch data, inject a fake API (from the architecture lesson) or mock the module, and use findByText (which waits) for content that appears after loading.
Worked example: an e2e flow with Maestro
Maestro drives the app through the accessibility tree. A flow is a YAML file:
appId: com.yourname.studytimer
---
- launchApp
- tapOn: "Add one"
- assertVisible: "Count: 1"Run it with maestro test flows/counter.yaml against a running Android emulator or iOS Simulator (Maestro's CLI runs on macOS, Linux and Windows; the iOS Simulator needs a Mac). Detox does the same job with JavaScript tests and waits automatically for animations and network calls to settle, at the cost of a more involved setup. EAS can run e2e tests on its build servers as part of a build.
What to test
- Every reducer and pure function: unit tests.
- Every screen's main states: loading, data, empty, error.
- Interactions that change state: taps, typing, toggles.
- One or two e2e journeys per critical flow.
- Bugs: write a failing test that reproduces each fixed bug, so it stays fixed.
Common mistakes
- Testing implementation details (state variable names, internal functions) so tests break on every refactor. Test what the user sees.
- Snapshot tests of whole screens that everyone updates without reading.
- Real network calls in unit or component tests: slow and flaky. Mock or inject.
- Too many e2e tests: they are slow and brittle. Keep them for critical paths.
- Timing-based waits (
sleep(2000)). UsefindBy...queries or the e2e tool's built-in waiting.
Quick recap
- Many unit tests, a solid layer of component tests with React Native Testing Library, a few e2e flows with Maestro or Detox.
- Query by role and text, the way a user finds things.
- Mock the network, cover loading/empty/error states and pin every fixed bug with a test.
Try it
The Do tab has you add Jest and React Native Testing Library to your project, write a component test, and optionally run a Maestro flow on an emulator.
Key terms (5)
- Unit tests
- Fast tests of plain functions and reducers, run with Jest.
- Component tests
- Tests that render one component or screen in a simulated environment and interact with it like a user.
- End-to-end (e2e) tests
- Slow tests that drive the real app on a simulator, emulator or device through critical journeys.
- React Native Testing Library
- A library used with Jest to render components and query them by role and text.
- Maestro
- An e2e testing tool whose flows are written in YAML and drive the app through the accessibility tree.