Rn Write Tests
ASecurityUse to write tests for an Expo + RN app: Jest + React Native Testing Library for unit/integration (components, hooks, queries, mutations) and Maestro for end-to-end flows (sign-in, navigation, forms). Sets up the testing stack on first call (jest-expo preset, RNTL, jest config) and writes a focused test next to the source file. Triggers on: "write tests for X", "add e2e test", "two-user flow", "multi-user test", "test con due utenti", "shared state between users", "mock expo-notifications", "...
- 6 stars
- 0 votes
- 0 copies
- 0 views
- Added October 19, 2026
Works with
Security analysis
96/100- Installs packages at runtime which could introduce malicious dependencies
Pro scans all 6 files and shows the line behind each finding
npx -y skills add lukedj78/dev-flow --skill rn-write-tests --agent claude-codeAre you the author of Rn Write Tests?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/lukedj78-rn-write-tests)---
name: rn-write-tests
description: 'Use to write tests for an Expo + RN app: Jest + React Native Testing Library for unit/integration (components, hooks, queries, mutations) and Maestro for end-to-end flows (sign-in, navigation, forms). Sets up the testing stack on first call (jest-expo preset, RNTL, jest config) and writes a focused test next to the source file. Triggers on: "write tests for X", "add e2e test", "two-user flow", "multi-user test", "test con due utenti", "shared state between users", "mock expo-notifications", "test this hook". Not for: choosing what to test (the user decides), running the existing test suite (just `npm test`), testing pure logic outside an Expo project, or a Next.js web app (write-tests).'
---
# rn-write-tests — Jest + RNTL + Maestro testing for Expo + RN
## Contract
See `references/contracts.md` (vendored from `dev-flow`). Key facts:
- Reads `<project-root>/.workflow/meta.json#stack.framework` — must be `"expo-rn"`.
- Adds dev dependencies + Jest config on first call (idempotent: detects existing setup).
- Writes test files under `<project-root>/__tests__/` (Jest convention) for unit/integration.
- Writes Maestro YAML flows under `<project-root>/.maestro/` for e2e.
- Does NOT modify `meta.json#phase`.
## When this skill applies
- User asks to write a test for a specific source file or flow.
- Orchestrator does NOT route here automatically — test writing is on-demand.
## Knowledge dependencies (read these first)
- `rn-fundamentals/SKILL.md` — TS strict, modern primitives the tests will see.
- `rn-data-fetching/references/patterns.md` — query/mutation patterns the tests must exercise.
## Workflow
### Step 1 — Verify preconditions
Read `.workflow/meta.json`. Abort if `stack.framework != "expo-rn"`.
### Step 2 — Detect / install the test stack
Check if `jest`, `jest-expo`, `@testing-library/react-native` are in `package.json`. If not:
```bash
npx expo install --dev \
jest jest-expo \
@testing-library/react-native@^14 \
test-renderer@^1 \
@types/jest -- --legacy-peer-deps
# jest-expo declares `@react-native/jest-preset` as a peer (58: `^0.88.0`), and that package is
# NOT in the SDK's bundledNativeModules — so `expo install` hands you npm latest, which breaks as
# soon as it is a minor ahead of the project's RN (SDK 57, 2026-09-08: 0.87.1 against RN 0.86).
# Pin it to the React Native version the project actually has:
RN_VERSION="$(node -p "require('react-native/package.json').version")"
npm install --save-dev --legacy-peer-deps "@react-native/jest-preset@${RN_VERSION}"
```
(Without the `@react-native/jest-preset` line the very first `jest` run dies with *"The React Native Jest preset that jest-expo relies on has moved to a separate package"*; with the wrong minor it dies with *"Could not locate module react-native/setup-env"* — observed 2026-09-08 on SDK 57 / RN 0.86.3. On SDK 58 the suite also needs `"resolver": "react-native-reanimated/jest/resolver"` in the jest config, or `setUpTests()` crashes every suite — re-run 2026-10-17 on a fresh SDK 58 app. `test-renderer@^1` is a **required** peer dep of RNTL v14 — it replaced `react-test-renderer`. Node `^22.13 || >=24` is also required. Native matchers like `toBeOnTheScreen()` are built into `@testing-library/react-native` v12.4+ — no separate `@testing-library/jest-native` needed. See `references/jest-setup.md` for the full config.)
⚠️ **RNTL v14 is async**: `render`, `renderHook`, `fireEvent` and `act` all return Promises and MUST be awaited, and the `UNSAFE_*` queries are gone. Read the v14 section of `references/rntl-patterns.md` before writing a test — a missing `await` produces a test that passes for the wrong reason. Migrating an existing suite: `npx codemod@latest rntl-v14-update-deps --target .` then `npx codemod@latest rntl-v14-async-functions --target ./src`.
For Maestro: detect `.maestro/` directory. If missing AND user wants e2e, see `references/maestro.md` (manual install, not npm — Java 17+, `.maestro/` flow layout, `testID` conventions, and ⚠️ the Expo Go vs dev-build caveat: Expo Go can't `launchApp` your own `appId`, use `openLink: exp://…`).
### Step 3 — Choose what to test
Ask the user (one round-trip) what to test:
- **Component** (renders, props, interactions) → RNTL.
- **Hook** (`useQuery`, `useMutation`, custom hook) → RNTL `await renderHook(...)`.
- **Pure function** (utility, helper) → plain Jest.
- **e2e flow** (sign-in, navigation, form submit) → Maestro.
- **Shared-state / two-user flow** (user A takes a seat, a slot, an invite; user B must see it taken) → Maestro, two `runFlow` sign-ins in one file with `clearState` between them, against a real test backend — `references/maestro.md` §Two-user flows. Jest cannot express this; it is the check that finds authorization bugs between identities.
**On a project whose `stack.test` carries the `e2e` layer**, the agentic runner (`@e2e-dev/mobile`,
built on agent-device) is the other way to write an e2e flow — a native accessibility tree the agent
drives, on simulators, emulators and connected phones. There is **one copy of that text**, in
`write-tests/references/e2e-agentic.md`: one technique, one authority, whichever platform asks.
Read it before writing one, for the cost rule and for the gates. ⚠️ **It does not take the two-user
flow from Maestro**: e2e on mobile has no session save/restore, so that check stays where it is.
If the file under test is `lib/api.ts`, write a Jest test of the function with `fetch` mocked.
If the file under test is `app/(auth)/sign-in.tsx`, write an RNTL test of the screen + a Maestro flow of the user journey.
### Step 4 — Write the test
Use the canonical patterns from `references/rntl-patterns.md` (or `references/jest-setup.md` for utilities, or `references/maestro.md` for e2e).
Test files:
- Unit/integration: `__tests__/<mirror-source-path>.test.tsx` (or `.test.ts` for non-JSX).
- e2e: `.maestro/<flow-name>.yaml`.
### Step 5 — Run the test
```bash
cd <project-root> && npm test -- --runInBand --bail
```
(For Maestro: `maestro test .maestro/<flow-name>.yaml`.)
Test MUST pass. If it doesn't:
1. Read the failure. Is it a missing mock? An assumption about the test API?
2. Fix the test (NOT the source — that's a separate task).
3. Re-run.
### Step 6 — Commit
```bash
git add __tests__/<path>.test.tsx
git commit -m "test(<area>): cover <what>"
```
## Common anti-patterns (NEVER do)
- ❌ **Detox** — heavy, Expo Go unfriendly, requires native build. Use Maestro.
- ❌ **Enzyme** — abandoned. RNTL only.
- ❌ Snapshot for every component — only for stable design-system primitives.
- ❌ `jest.fn()` without typing the return → tests pass on wrong shape.
- ❌ Testing implementation details (state names, internal function calls) → brittle. Test user-facing behavior.
- ❌ Testing TanStack Query result shape directly → mock the queryFn instead and assert UI.
- ❌ Skipping cleanup → tests leak state. Always use RNTL's auto-cleanup (default).
- ❌ Sleeping `await new Promise(r => setTimeout(r, 1000))` → use `waitFor` with explicit assertions.
## Updating meta.json (recommended pattern)
This skill does NOT advance `meta.json#phase` (see Contract above) — never call `set-phase`. It only records the artifact and appends history, using the canonical script when available:
```bash
# Wherever dev-flow is installed (e.g. ~/.claude/skills/dev-flow/), invoke:
python3 .../dev-flow/scripts/update_meta.py <project-root> record-artifact \
--path <relative-path> --produced-by '<this-skill-name>' [--derived-from <p1> <p2> ...]
python3 .../dev-flow/scripts/update_meta.py <project-root> append-history \
--skill '<this-skill-name>' --inputs '{...}' --outputs '{...}' --phase-after <current_phase>
```
The script normalizes legacy kebab-case aliases (e.g. `module-added` → `module_added`) and writes the canonical sha256 + timestamp into `meta.json#artifacts`. **Fall back to direct JSON editing only if the script is not on PATH** (and warn the user).
## Sources
- Course: codewithbeto.dev/rnCourse — "Testing" module (paid, distilled).
- Official: https://callstack.github.io/react-native-testing-library/
- Official: https://docs.expo.dev/develop/unit-testing/
- Official: https://maestro.mobile.dev/
Files in this skill
- SKILL.md
- references/contracts.md
- references/decision-tree.md
- references/jest-setup.md
- references/maestro.md
- references/rntl-patterns.md
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.