Testing and Performance for React Native Apps

React Native with Expo: 10 Real Projects · Advanced Apps and Release · 6 min read · Updated

Where tests fit

  1. Most tests check plain functions. They run in milliseconds.
  2. Component tests render a screen and act like a user.
  3. A few checks on a real device catch what mocks cannot, such as camera and notifications.
  4. When all pass, you can release with more confidence.

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, tune initialNumToRender and windowSize, 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.memo on a row component, and useCallback for 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

  1. Set up Jest and run the three function tests.
  2. Add a test for a case you can think of that is not covered.
  3. Run the component test and add a second one for the round-up switch.
  4. Break a function on purpose, see the test fail, then fix it.

How to check it worked

  • npm test passes.
  • You can explain why pure functions are easy to test.
  • You can name three ways to keep a long list smooth.

Frequently asked questions

Jest or Vitest?

React Native projects use Jest through the jest-expo preset, because it handles the native environment for you.

How many tests should I write?

Cover rules that would hurt if they broke: money, validation, streaks and state changes. Do not chase 100 percent coverage of simple screens.

Why is my list slow only in development?

Development builds include extra checks and are slower. Always judge speed on a release build on a real phone.