Tests are small programs that check your code automatically. They let you change code without fear and catch mistakes before users do. React Native projects use Jest to run tests and React Native Testing Library to render components and act like a user: find text, type and press.
Set up testing
Terminal and package.json
npx expo install jest-expo jest @types/jest --dev
npm install --save-dev @testing-library/react-native
// package.json
{
"scripts": { "test": "jest" },
"jest": { "preset": "jest-expo" }
}
// run
npm test
Test files go in a __tests__ folder or end in .test.ts or .test.tsx. npm test runs them all.
Test pure functions first
The best tests are for plain functions: the tip maths, the money helpers and the streak counter. They are fast and need no phone. Each test has a name that says what should happen, then a setup, an action and a check.
`__tests__/tip.test.ts`
import { calculateTip } from "../tip";
describe("calculateTip", () => {
it("adds 15 percent and splits the bill", () => {
const r = calculateTip(100, 15, 3, false);
expect(r.tip).toBeCloseTo(15);
expect(r.total).toBeCloseTo(115);
expect(r.perPerson).toBeCloseTo(38.33, 2);
});
it("rounds the tip up when asked", () => {
expect(calculateTip(33, 15, 1, true).tip).toBe(5);
});
it("rejects zero people", () => {
expect(() => calculateTip(10, 10, 0, false)).toThrow();
});
});
`__tests__/money.test.ts`
import { asMoney, parseCents } from "../money";
describe("money helpers", () => {
it.each([
["12", 1200],
["12.5", 1250],
["12.50", 1250],
["0.99", 99],
])("parses %s", (text, cents) => {
expect(parseCents(text)).toBe(cents);
});
it.each(["abc", "", "-3", "1.234", "0"])("rejects %s", (text) => {
expect(parseCents(text)).toBeNull();
});
it("formats cents", () => {
expect(asMoney(1970)).toBe("19.70");
});
});
`__tests__/streak.test.ts`
import { streak } from "../habits";
const today = new Date(2026, 10, 4); // 4 November 2026
describe("streak", () => {
it("counts consecutive days ending today", () => {
expect(streak(["2026-11-04", "2026-11-03", "2026-11-02"], today)).toBe(3);
});
it("keeps the streak alive if today is not done yet", () => {
expect(streak(["2026-11-03", "2026-11-02"], today)).toBe(2);
});
it("stops at a gap", () => {
expect(streak(["2026-11-04", "2026-11-02"], today)).toBe(1);
});
it("is zero with no recent days", () => {
expect(streak(["2026-10-01"], today)).toBe(0);
});
});
it.each runs the same check for many inputs. Notice that the streak test passes the date in as a parameter. Code that takes the current time from outside is far easier to test than code that reads the clock itself.
Test a component
This test renders the tip calculator from project 1, types a bill like a user and checks the visible result. Place tip.ts and App.tsx at the project root so the imports match.
`__tests__/TipScreen.test.tsx`
import { fireEvent, render, screen } from "@testing-library/react-native";
import App from "../App";
describe("Tip calculator screen", () => {
it("shows the total for a typed bill", () => {
render(<App />);
fireEvent.changeText(screen.getByPlaceholderText("0.00"), "100");
expect(screen.getByText("Total: 115.00")).toBeTruthy();
});
it("splits the bill when a person is added", () => {
render(<App />);
fireEvent.changeText(screen.getByPlaceholderText("0.00"), "100");
fireEvent.press(screen.getByText("+"));
fireEvent.press(screen.getByText("+"));
expect(screen.getByText("Each person pays 38.33")).toBeTruthy();
});
});
Find elements by what the user sees (text, placeholder, role) rather than by internal details. If you must target something with no text, give it a testID and use getByTestId.
Component tests for screens that use native modules (SQLite, camera, notifications) need those modules mocked. Test their logic through the small functions around them instead, as the data layers in this course allow.
Performance habits
- Use FlatList, not ScrollView, for long lists. Give it a stable
keyExtractor. For very large lists, tuneinitialNumToRenderandwindowSize, or try a faster list library such as FlashList. - Keep render cheap. Do heavy calculations outside render or wrap them in
useMemo. - Memoise rows when needed.
React.memoon a row component, anduseCallbackfor handlers you pass to it, avoid redrawing every row. - Do not create huge inline objects or functions inside long lists when a stable reference will do.
- Size images properly. Do not load a 4000 pixel photo into a 100 pixel thumbnail. Consider the expo-image library for caching.
- Measure, do not guess. Use the React DevTools Profiler and the performance monitor in the developer menu to see slow renders and dropped frames.
- Test the release build. Development mode is slower than a release build, so judge speed on a release build.
Preventing crashes
- Handle every network, storage and permission error with a visible message.
- Use TypeScript and optional chaining for data that might be missing.
- Wrap risky screens with an error boundary, or use the error boundary Expo Router provides per route.
- Test with a slow network, airplane mode, large text and dark mode.
Your task
- Set up Jest and run the three function tests.
- Add a test for a case you can think of that is not covered.
- Run the component test and add a second one for the round-up switch.
- Break a function on purpose, see the test fail, then fix it.
How to check it worked
npm testpasses.- You can explain why pure functions are easy to test.
- You can name three ways to keep a long list smooth.