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 tests (many, fast): plain functions and reducers. Price rules, validation, the navigation and cache logic you wrote earlier. Run with Jest.
- Component tests (a good number): render one component or screen in a simulated environment and interact with it like a user. Use React Native Testing Library (RNTL) with Jest.
- End-to-end (e2e) tests (a few, slow): drive the real app on a simulator, emulator or device, for the journeys that must never break (sign in, checkout). Use Maestro (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 --dev
Add 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.