Jest + Testing Library
Set up Jest + Testing Library for testing in an Expo app with ReadyNative: jest-expo preset, RNTL, tests for stores/theme/ui
Expo Go: yes
Runs in Expo Go; no dev build needed for this module.
Jest on jest-expo, with React Native Testing Library and the mocks a real Expo app needs. I'd keep this on - it's what makes swapping modules safe, since each module ships its own tests and they disappear with it. Pick testing/none only if you're bringing another runner. Tests import from @jest/globals and render screens wrapped in the kit's ThemeProvider, so they're stack-agnostic.
Setup
bun run setup --testing jestThat's it - setup adds jest.setup.ts (in-memory expo-sqlite/kv-store, a virtual expo-localization, plus reanimated, haptics and symbols mocks), the core tests under src/**/__tests__/ (stores, theme, env, the i18n shim, the kit's Button, List, Row and Toast, the Settings screen), the dev dependencies (jest, jest-expo, @testing-library/react-native, @jest/globals, react-test-renderer, types) and the test script.
Usage
bun run test # everything
npx jest src/stores # one folder
npx jest --watch # while you workA test looks like this. React Native Testing Library 14's render returns a promise, so await it:
import { expect, it } from "@jest/globals";
import { render, screen } from "@testing-library/react-native";
import { ThemeProvider } from "@/components/ui";
import { SettingsScreen } from "@/screens/settings/settings-screen";
it("renders the title", async () => {
await render(
<ThemeProvider>
<SettingsScreen />
</ThemeProvider>
);
expect(screen.getByText("Settings")).toBeTruthy();
});That's the finalized tree; before setup, or with --keep-modules, import ThemeProvider from @/ui. Wrap in whatever else the screen reads - a screen that runs a query needs your query provider too.
Module-specific tests (client.test.ts, field.test.tsx, i18n.test.tsx, and example-store.test.ts with --with-examples) are owned by their modules, so they arrive and leave with the module.
Gotchas
jest.config.jsis a core file, not part of this module, because the setup engine shares it. That meanstesting/noneleaves it in place - harmless, unused.- Each UI stack adds its own jest setup where it needs one (
src/components/ui/jest-setup.tsafter setup), required byjest.setup.ts. Unistyles' official mock stripsvariants; Tamagui needs its ESM entries intransformIgnorePatterns(already handled). - Picking
--testing noneat setup (or later in a--keep-modulestree) removes the setup file, every test file and the dev dependencies. In a finalized tree, delete them by hand.
Remove it
While modules/ exists (a tree set up with --keep-modules), setup does all of it:
bun run setup --testing none --yes --keep-modulesIn a finalized tree setup is a stub, so you undo it by hand. Here is everything this module added:
- Delete the files that are still there:
jest.setup.ts,src/lib/__tests__/env.test.ts,src/lib/__tests__/i18n-shim.test.ts,src/lib/__tests__/analytics-shim.test.ts,src/lib/__tests__/confirm.test.ts,src/lib/weather/__tests__/weather.test.ts,src/lib/__tests__/cache.test.ts,src/lib/__tests__/native-toast.test.ts,src/lib/__tests__/privacy.test.ts,src/screens/__tests__/developer-tools.test.tsx,src/screens/__tests__/settings-screen.test.tsx,src/stores/__tests__/create-store.test.ts,src/stores/__tests__/onboarding.test.ts,src/stores/__tests__/consent.test.ts,src/stores/__tests__/theme-mode.test.ts,src/theme/__tests__/mode.test.ts,src/theme/__tests__/mode.web.test.ts,src/theme/__tests__/tokens.test.ts,src/ui/__tests__/button.test.tsx,src/ui/__tests__/list.test.tsx,src/ui/__tests__/row.test.tsx,src/ui/__tests__/toast.test.tsx,src/lib/__tests__/store-review.test.ts,src/lib/__tests__/clipboard-share-browser.test.ts,src/components/__tests__/updates-banner.test.tsx,jest.config.js,scripts/test-utils/fixture.ts,scripts/__tests__/apply.test.ts,scripts/__tests__/doctor.test.ts,scripts/__tests__/e2e.test.ts,scripts/__tests__/gen-links.test.ts,scripts/__tests__/gen-privacy.test.ts,scripts/__tests__/graph.test.ts,scripts/__tests__/pm.test.ts,scripts/__tests__/store-check.test.ts,scripts/__tests__/gen-theme.test.ts,scripts/__tests__/gen-theme-tamagui.test.ts,scripts/__tests__/gen-theme-nativewind4.test.ts,scripts/__tests__/generate.test.ts,scripts/__tests__/registry.test.ts,scripts/__tests__/resolve.test.ts,scripts/__tests__/scaffold-clean.test.ts,scripts/__tests__/setup-clean.test.ts,scripts/__tests__/finalize.test.ts,scripts/__tests__/scaffold-tab.test.ts,scripts/__tests__/__snapshots__/gen-theme.test.ts.snap,scripts/__tests__/__snapshots__/gen-theme-tamagui.test.ts.snap,scripts/__tests__/__snapshots__/gen-theme-nativewind4.test.ts.snap,scripts/__tests__/__snapshots__/generate.test.ts.snap,scripts/__tests__/gen-assets.test.ts,src/components/__tests__/error-boundary.test.tsx,src/hooks/__tests__/use-sign-out-cleanup.test.tsx. - Uninstall the dependencies:
bun remove @jest/globals @testing-library/react-native @types/jest @types/react-test-renderer jest jest-expo react-test-renderer. - Delete the script
testfrompackage.json. - Check it:
bun run typecheckandbun run lintpoint at anything that still imports the removed files;bun run gen:graphrefreshesdocs/ARCHITECTURE.md.
Reference
Everything below is generated from modules/testing/jest/module.json - the same file bun run setup reads, so it is what actually lands in your repo.
Install
bun run setup --testing jestModule id: testing/jest (the default for this category).
Dependencies
| Package | Version | Kind |
|---|---|---|
@jest/globals | ~29.7.0 | devDependency |
@testing-library/react-native | ^14.0.1 | devDependency |
@types/jest | 29.5.14 | devDependency |
@types/react-test-renderer | ^19.3.0 | devDependency |
jest | ~29.7.0 | devDependency |
jest-expo | ~57.0.5 | devDependency |
react-test-renderer | 19.2.3 | devDependency |
Files
52 files copied to the project root
jest.setup.tssrc/lib/__tests__/env.test.tssrc/lib/__tests__/i18n-shim.test.tssrc/lib/__tests__/analytics-shim.test.tssrc/lib/__tests__/confirm.test.tssrc/lib/weather/__tests__/weather.test.tssrc/lib/__tests__/cache.test.tssrc/lib/__tests__/native-toast.test.tssrc/lib/__tests__/privacy.test.tssrc/screens/__tests__/developer-tools.test.tsxsrc/screens/__tests__/settings-screen.test.tsxsrc/stores/__tests__/create-store.test.tssrc/stores/__tests__/onboarding.test.tssrc/stores/__tests__/consent.test.tssrc/stores/__tests__/theme-mode.test.tssrc/theme/__tests__/mode.test.tssrc/theme/__tests__/mode.web.test.tssrc/theme/__tests__/tokens.test.tssrc/ui/__tests__/button.test.tsxsrc/ui/__tests__/list.test.tsxsrc/ui/__tests__/row.test.tsxsrc/ui/__tests__/toast.test.tsxsrc/lib/__tests__/store-review.test.tssrc/lib/__tests__/clipboard-share-browser.test.tssrc/components/__tests__/updates-banner.test.tsxjest.config.jsscripts/test-utils/fixture.tsscripts/__tests__/apply.test.tsscripts/__tests__/doctor.test.tsscripts/__tests__/e2e.test.tsscripts/__tests__/gen-links.test.tsscripts/__tests__/gen-privacy.test.tsscripts/__tests__/graph.test.tsscripts/__tests__/pm.test.tsscripts/__tests__/store-check.test.tsscripts/__tests__/gen-theme.test.tsscripts/__tests__/gen-theme-tamagui.test.tsscripts/__tests__/gen-theme-nativewind4.test.tsscripts/__tests__/generate.test.tsscripts/__tests__/registry.test.tsscripts/__tests__/resolve.test.tsscripts/__tests__/scaffold-clean.test.tsscripts/__tests__/setup-clean.test.tsscripts/__tests__/finalize.test.tsscripts/__tests__/scaffold-tab.test.tsscripts/__tests__/__snapshots__/gen-theme.test.ts.snapscripts/__tests__/__snapshots__/gen-theme-tamagui.test.ts.snapscripts/__tests__/__snapshots__/gen-theme-nativewind4.test.ts.snapscripts/__tests__/__snapshots__/generate.test.ts.snapscripts/__tests__/gen-assets.test.tssrc/components/__tests__/error-boundary.test.tsxsrc/hooks/__tests__/use-sign-out-cleanup.test.tsx