react-brain
Browse decisions
React & language foundations 3App architecture 11UI 13Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness benchnewsletterarchitecturechangelogmethodologyroadmap

entriestooling-ops · verified 2026-10-01 · react + react-native

Testing strategy & tooling

reviewedconfidence: highthis tier holds 83% on the public scorecard (6/11 graded · 0 overturned) →

related decisions: p2p · a11y

cited by: p2p · ai-devtools

re-verified 9× — 2026-10-08 · 2026-09-24 · 2026-09-21 · 2026-09-10 · 2026-09-01 · 2026-08-18 +3 · changelog

recommendation

Unit/component: Vitest (web) or Jest (RN) + React Testing Library. E2E: Playwright (web), Maestro (RN). MSW for API mocks; Storybook for component dev.

  • RN e2e → Maestro (Expo's recommendation; Expo archived Detox)
  • existing Detox suite → Maestro for new flows (Expo archived Detox; Detox itself still publishes, 20.51.4)
  • API-heavy tests → MSW (the same mocks serve unit + e2e + dev)
  • Holepunch / Bare app → brittle (the ecosystem's TAP runner; idiomatic, not a deviation — see RB-E-P2P)
  • unit/integration tests that must exercise real NATIVE modules → react-native-harness (early; pin versions)
  • web component tests in a real browser → Playwright (1.62+ stories + galleries), else Storybook 10 when the workshop is already there
  • native E2E suite too flaky to block CI → fix the E2E API first (assert every step, find elements visually, gate new tests on repeated runs — see the Shopify reading)
  • RN debugging inside VS Code or Cursor → Radon IDE (device panel, click-to-inspect)
  • an AI agent must drive the iOS Simulator → Maestro MCP (maestro mcp), else Argent (experimental)
  • test discipline (what to test, TDD) → agentic-engineering-patterns

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoffevidence
Jest (30.5) / Vitest (5)Unit runners: Vitest is faster on web, Jest is the RN default. Both majors converged on per-argument mocking (Jest `fn.whenCalledWith`, Vitest `vi.when(spy).calledWith`). jest 30.5.2 (2026-09-18), vitest 5.0.1 (2026-09-15; both verified vs npm). See note: JEST 30.5, VITEST 5.56.8M/wk · ships in 21/34
React Testing Library (14)Behavior-first component tests with web and RN variants; the 14 is RN Testing Library, which adds React 19 support + async APIs and renders host elements only. @testing-library/react-native 14.0.1 (2026-06-23), web @testing-library/react 16.3.3 (2026-08-27; both verified vs npm). See note: RNTL RENDERER.79.9M/wk · ships in 14/34
MSW (Mock Service Worker)Network-level API mocking; the same mocks serve unit, e2e and dev. msw 3.0.1 (2026-09-30, verified vs npm) is a breaking major — RN projects on `msw/native` should hold on 2.x. See note: MSW 3.0.26.8M/wk · ships in 9/34
Playwright (1.63) / Maestro / Detox / MeticulousE2E: Playwright for web; Maestro is Expo's recommended native e2e and bundles an MCP server for coding agents; Expo archived Detox; Meticulous is record-based AI e2e. @playwright/test 1.63.0 (2026-09-04), detox 20.51.4 (2026-06-16; both verified vs npm), Maestro CLI 2.10.0 (2026-08-31, verified vs GitHub). See note: PLAYWRIGHT 1.62, MAESTRO.86.2M/wk · ships in 8/34
Storybook 10Component workshop; web + RN run side by side; Vite 8 + Playwright/Vitest test integration + MCP. storybook 10.6.0 (2026-09-02, verified vs npm; 11 is in alpha).ships in 12/34
Radon IDE (Software Mansion)VS Code / Cursor RN debugging: device panel, click-to-inspect, React Profiler.
Argent (Software Mansion) — NEW/experimentalAgentic toolkit that gives an AI assistant control of the iOS Simulator (drive + inspect the running app) to shorten the debug loop; new, niche. v0.25.2 (2026-09-18, verified vs GitHub releases).
react-native-harness (Callstack) — NEWJest-style describe/it tests running ON real devices/simulators (iOS/Android/web) with native-module access — the layer between Node-Jest and black-box Maestro. 1.5.0 (2026-09-14, verified vs npm + GitHub) adds `harness ci` for unattended runs; young, pin it. See note: REACT-NATIVE-HARNESS.46k/wk

evidence: npm weekly downloads (signals snapshot) · “ships in n/D” = adoption across the production-app census, honest denominators

Migration lines

version and deprecation lines this decision tracks — for YOUR repo's sequenced plan: the doctor or npx -y @heart-it/react-brain migrate .

react-test-renderer → React Native Testing Library v14 (host-element role/label queries) [deprecated · effort M]

react-test-renderer is deprecated by React core; RNTL v14 runs its own react-reconciler-based test renderer rendering host elements only — don't reach for react-test-renderer in new test code

github.com/mdjastrzebski/test-renderer

npm weekly downloads (from the corpus's last signals run): jest 56.8M · vitest 142.1M · @testing-library/react-native 5.1M · @testing-library/react 79.9M · @playwright/test 86.2M · detox 576k · msw 26.8M · brittle 39k · react-native-harness 46k

Verified notes

Test behavior the way users exercise it, at the cheapest layer that gives confidence: Vitest (web) or Jest (RN) with Testing Library for components, MSW so one set of network mocks serves every layer, and one e2e tool per platform — Playwright on web, Maestro on native. Maestro holds the native default because Expo recommends it and archived its Detox docs, and because it is where agent-driven testing is consolidating. On Jest vs Vitest the platform decides, not taste: Jest is RN's preset. JEST PRESET MOVED (verified vs the RN 0.85 release blog, 2026-04-07): RN 0.85 moved the Jest preset out of the core package — set `preset: '@react-native/jest-preset'` in jest.config.js (was `preset: 'react-native'`). JEST 30.5 (2026-08-28, verified vs the v30.5.0 release): `fn.whenCalledWith(...)` per-argument mocking (asymmetric matchers accepted, toHaveBeenCalledWith equality semantics), describe-level retries via jest.retryTimes(), and a new file watcher; its own notes flag a big jest-runtime/resolve/haste-map change with regression risk → upgrade with a full-suite run, not a lockfile bump. VITEST 5 (2026-09-03, verified vs the release + the announcement post): a performance release with numbers published as a reproducible benchmark suite (vitest-dev/benchmarks — generated apps from a 5-file utility package to a 1,280-module monolith, across every pool × environment). Wins: −53% on a deps-heavy app and −15..−25% on vm pools, Browser Mode and large isolated suites, but ±3% where environment setup already dominates (forks + jsdom + isolation) → when the reporter says `environment 79%`, test speed is a jsdom-version problem, not a runner problem (the Sentry RTL reading), and Vitest 5 will not fix it. Mechanisms: inline projects share one Vite server, fsModuleCache graduated from experimental (on-disk transform cache reused across runs and processes), fewer main↔worker round trips, vm pools reuse compiled code, and Vitest bundles its own deps. New surface: `vitest doctor` re-runs your suite under alternative configurations and recommends faster options (performance hints print by default); a provider-independent Trace View for Browser Mode records interactions/assertions as DOM snapshots replayable in the UI and HTML reporter (no separate viewer, unlike Playwright traces); `vi.when(spy).calledWith(...)` with a `toHaveBeenExhausted` assertion; inline projects inherit the root config by default (`extends: true` no longer needed), and config files in `test.projects` may nest their own projects. Breaking changes: pretty-format replaces loupe.inspect (snapshot/error output shifts), `sequential` options removed in favor of `concurrent`, locators are objects rather than strings, deprecated entry points removed, and `bench` is no longer a top-level import but a test-context fixture inside a normal `test()`. RNTL RENDERER (2026-07-10, verified vs the docs + the mdjastrzebski/test-renderer repo): RN Testing Library v14 runs on its own react-reconciler-based test-renderer that renders HOST elements only — which is why its query model moved to host-element/role queries. React core's react-test-renderer is deprecated → do not use it in new test code. PLAYWRIGHT 1.62 (released 2026-07-24, verified 2026-07-30 vs the release notes + registry; 1.63.0 is current, verified vs npm 2026-09-24): component testing is rebuilt around STORIES + GALLERIES — a story wraps a component in a scenario, a gallery renders stories on demand, and `mount('components/Expandable/Stateful')` navigates to the gallery and returns a scoped Locator → "Storybook for the workshop, Playwright for e2e" is no longer the only sensible split on web. Also: an `AbortSignal` `signal` option on most operations, WebP screenshots for visual comparison (quality 100 = lossless), and `testConfig.retryStrategy: 'isolated'`, which defers all retries to the end and runs them one by one in a single worker — the fix for flaky retries that interfere with the rest of the suite. MAESTRO (verified vs docs.maestro.dev, 2026-07-07): Maestro 2.0 added AI test-healing, and Maestro bundles an MCP server so coding agents can drive simulators/emulators/devices, inspect UI and generate E2E flows (`claude mcp add maestro -- maestro mcp`). Detox has a 'Copilot' beta, but Expo archived its Detox docs, so Detox is a legacy suite to maintain, not a new pick. REACT-NATIVE-HARNESS (2026-09-14, verified vs the v1.5.0 release and npm): v1.5 adds Windows platform support, `harness ci` commands and a Metro config enhancer. The CI commands are the meaningful part: an on-device test layer is only useful once it runs unattended. MSW 3.0 (3.0.0 shipped 2026-09-28, 3.0.1 bugfix 2026-09-30, verified vs the GitHub release notes, the official integration docs and npm 2026-10-01): ESM-only now, drops Node 18/20 (floor is Node 22) and requires TypeScript 5.9+. The RN-relevant break: `msw/native` is removed in favor of `@msw/react-native` (docs: install `msw @msw/react-native`, replace `setupServer` from `msw/native` with `network` from the new package — it preconfigures `network` for RN and polyfills the standard APIs RN lacks) — BUT the package is NOT YET on the public npm registry (checked 2026-10-01: 404), so the documented migration path isn't actually installable yet; the mswjs/react-native GitHub repo is still labeled an experiment. Other breaks: `graphql` moves to the `msw/graphql` subpath (now an optional peer dep), `handleRequest()` is removed for `defineNetwork()`, `onUnhandledRequest` is renamed `onUnhandledFrame`, cookies are no longer Node-managed, and `worker.stop()` returns a Promise. Consequence: web-only projects can take 3.0 after an ESM/Node-22 check; RN projects using `msw/native` stay on 2.x until `@msw/react-native` actually publishes — re-verify npm before upgrading, the docs are ahead of the release. CROSS-REFERENCES: RB-E-P2P owns Holepunch testing (brittle); RB-E-A11Y shares the role-query thread (TkDodo reading); agentic-engineering-patterns owns test discipline. HISTORY: 2026-10-01 REOPENED: MSW 3.0 (ESM-only, Node 22 floor, `msw/native` removed; the official docs name and document `@msw/react-native` as the replacement, but it is not yet on the public npm registry — RN projects should hold on 2.x and re-check npm before migrating); added the Kent C. Dodds "You Own the Outcome" reading (verify AI-written code by outcome, not by reading every diff line). 2026-09-24 CORRECTED: react-native-harness 1.5 date 2026-09-17 → 2026-09-14 and Playwright 1.63.0 date 2026-09-10 → 2026-09-04 (npm publish times); Playwright 1.62 release date is 2026-07-24; "React Testing Library (14)" disambiguated — 14 is RNTL, web RTL is 16. 2026-09-21 REOPENED: react-native-harness pin 1.3 → 1.5 (Windows, `harness ci`, Metro enhancer). 2026-09-03 REOPENED: Vitest 5. 2026-08-28 REOPENED: Jest 30.5. 2026-07-30 REOPENED: Playwright 1.62 stories + galleries. 2026-07-10 REOPENED: RNTL v14 renderer flip; react-test-renderer deprecated. 2026-07-07 GAP CLOSED: Maestro MCP.

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

Making React Testing Library Tests 43% FasterScott Cooper (Sentry)

A 43% suite-time win with ZERO test-code changes — the whole gain landed upstream in jsdom, which reframes slow-RTL complaints as a dependency-version problem: a label-to-control index so getByRole stops rescanning the DOM per input (60.5ms → 0.67ms label reads), an un-tripped selector fast path fixed (getByRole queries ~42% faster), and event dispatch recording its target during path construction (12-36% by tree depth). Method note worth copying: AI-found bottlenecks, but every change had to survive Sentry's real codebase, not microbenchmarks, and had to land upstream where it stays maintained.

How we raised mobile end-to-end test stability to 98%Michael Garfinkle (Shopify Engineering)

The production evidence behind 'flaky native E2E is a framework problem, not a test problem'. Shopify's largest mobile app ran Appium via WebdriverIO with React Native test IDs since 2023 and had to pull the suite OUT of blocking CI; the rebuild put it back at 98% stability (individual test successes / total runs, up from 50%). Two changes did it. First, a builder-style API that makes flaky tests hard to write: every step carries an assertion (validated both ways — false before the action, true after), reusable named step sequences, and escape hatches deliberately named UNSAFE_ (UNSAFE_timeoutInSeconds, UNSAFE_testID) so they show up in review. Second, elements are found the way a user finds them — each step screenshots and matches visually, PaddleOCR for text and OpenCV for icons (grayscale, color-inverted variants, multiple sizes, adjacency rules to disambiguate duplicates) against Polaris design-system SVGs — which also removes the inspector round-trip from authoring and is why agents write correct tests on the first try. Plus a pre-promotion flakiness gate: a new test runs many times and is rejected above a failure threshold. Residual failures are network flakes and simulators failing to boot.

Test IDs are an a11y smellDominik Dorfmeister (TkDodo)

The bridge between this entry and RB-E-A11Y: querying by data-testid silently hides inaccessible markup, while role/label queries fail exactly when a real assistive-tech user would — so accessible queries are both the better test AND a free a11y audit. Sharpens the Testing-Library philosophy the two Dodds pieces establish.

You Own the Outcome: Product Engineering in the Age of AIKent C. Dodds

Reframes what 'ownership' means when an agent writes the code: agents produce output, you own the outcome. Practical shifts — align on the problem/success-criteria/constraints BEFORE asking for implementation; ask the agent for multiple options with tradeoffs, weighted by reversibility (one-way doors like a public API name vs two-way doors); and verify by outcome (tests, checks, does it actually work) rather than by reading every line of the diff, which only works if verification is automated and trustworthy.

Write tests. Not too many. Mostly integration.Kent C. Dodds

The foundational testing-strategy essay: maximize confidence per effort by favoring integration tests over mock-heavy unit suites and coverage-chasing. The origin of the Testing-Trophy mindset.

Testing Implementation DetailsKent C. Dodds

Why tests coupled to internals are brittle (false failures on refactor, false passes on breakage) and why to test behavior the way users use it. The mental model behind Testing Library.

Sources

Depth (in-domain rules) is owned by the agentic-engineering-patterns skill — this entry is selection breadth.

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About testing strategy & tooling in React & React Native

Diataxis: Explanation. This page builds understanding of how to think about testing — the reasoning behind the tool picks. It is not a tutorial, and the discipline (what to test, TDD, how much) is owned by the agentic-engineering-patterns skill. Read this for the why.

The one idea that organises everything: confidence per effort

Don't start from "unit vs integration vs e2e." Start from the question every test silently answers: how much confidence does this buy, for how much cost to write and maintain? That ratio — not dogma about test types — is the organising principle, and it produces the Testing Trophy: a few end-to-end tests, a strong middle of integration tests, fewer isolated unit tests, with static typing/linting as the base. Integration tests dominate because they exercise real component interactions (high confidence) without the brittleness of mock- heavy unit tests or the slowness of full e2e.

Two corollaries fall straight out, and they drive the tool choices:

  • Test behaviour, not implementation. Query by role/label like a user; don't assert on internal state or reach for data-testid as a crutch. Tests coupled to internals fail on refactors (false negatives) and pass on real breakage (false positives).
  • Mock at the boundary, not the module. Stub the network, not your own functions, so the same mocks serve unit, integration, and dev.

The default, and why

Unit/component: Vitest (web) or Jest (RN) + React Testing Library. E2E: Playwright (web), Maestro (RN). MSW for API mocks; Storybook for component dev.

The platform fork is about fit, not quality: Vitest is faster and Vite-native on the web; Jest remains the RN default (note: RN 0.85 moved the preset out of core — set preset: '@react-native/jest-preset', verified against the RN 0.85 blog). React Testing Library is the constant on both because it enforces the behaviour-not-implementation rule by construction. For e2e, Playwright owns the web; Maestro is Expo's recommendation for RN (Expo archived its Detox support). MSW is the keystone that makes the Trophy practical — one set of network mocks reused across every layer and in dev.

The landscape, and when each one wins

Jest / Vitest — the unit/component runners. Vitest on the web (speed, Vite integration), Jest on RN (default, ecosystem). Pick by platform, not preference.

React Testing Library (14) — behaviour-first component testing. The 14 is the React Native variant (RNTL), which adds React 19 support and async APIs and runs on its own host-element-only renderer; the web package is on 16.x. React core's react-test-renderer is deprecated — don't use it in new test code. It's not just a tool but a philosophy enforcer — its API makes implementation- detail testing awkward on purpose.

MSW (Mock Service Worker) — network-level API mocking. The reason it matters: the same handlers serve unit tests, integration tests, e2e, and local dev — so your mocks don't drift per layer. Reach for it the moment tests are API-heavy.

Playwright / Maestro / Detox / Meticulous — the e2e tier. Playwright for web; Maestro (AI test-healing) is the RN recommendation now that Expo archived Detox; Meticulous is record-based AI e2e. E2e is the smallest, slowest, highest-confidence layer — keep it few and critical-path.

react-native-harness — Jest-style tests that run on real devices and simulators with native-module access: the layer between Node-Jest and black-box Maestro, for tests that must exercise real native modules. Since 1.5 it runs unattended in CI (harness ci); still young, so pin it.

Storybook (10) — the component workshop (web + RN side by side), with Vitest/Playwright test integration. It's where component development and visual/interaction testing meet. On the web it is no longer the only home for component tests: Playwright 1.62 rebuilt component testing around stories and galleries, so browser-based component tests can live in Playwright.

Radon IDE — RN debugging in VS Code/Cursor (device panel, click-to-inspect, Profiler); a DX-adjacent aid to the loop rather than a test type.

Tradeoffs and failure modes to name out loud

  • Inverting the Trophy. Piling up mock-heavy unit tests feels productive but buys little confidence and breaks on every refactor. Push effort to the integration middle.
  • Testing implementation details. data-testid everywhere, asserting on state/private methods — the canonical brittleness source. Query like a user (this also improves a11y; role queries exercise the accessibility tree — see RB-E-A11Y).
  • Mocking your own modules instead of the network. Per-test function mocks drift from reality and from each other; MSW at the boundary keeps them honest.
  • E2e as the main suite. Slow, flaky-prone, expensive to maintain. It's the thin top of the Trophy, not the body.
  • Tests that aren't gated. A suite that doesn't run in CI rots — testing tooling only pays off inside the RB-E-DX feedback loop.

How it interacts with the rest of the stack

  • DX (RB-E-DX). Tests only protect you if CI runs them on every PR; the testing tools and the CI gate are two halves of one loop.
  • Data (RB-E-DATA). MSW lets you test data-fetching behaviour against realistic network responses without hitting real servers.
  • Accessibility (RB-E-A11Y). Role/label-based queries (the RTL default) double as an a11y check; test-IDs bypass that signal.
  • Discipline (agentic-engineering-patterns). What to test, when to write tests first, and how much is owned there — this page is which tools, that skill is the practice.

In one paragraph

Choose tests by confidence per effort, which yields the Testing Trophy: a strong integration middle, few e2e, unit where isolation helps, types/lint as the base. Test behaviour, not implementation (React Testing Library enforces it) and mock the network, not your modules (MSW, reused across every layer). Tooling forks by platform — Vitest/Jest + RTL for components, Playwright (web) / Maestro (RN) for e2e — and none of it pays off unless CI runs it (RB-E-DX); the discipline of what-to-test lives in agentic-engineering-patterns.


See also: RB-E-DX (CI gates the suite), RB-E-DATA (MSW + data-fetching tests), RB-E-A11Y (role queries as an a11y signal). TDD / what-to-test discipline: the agentic-engineering-patterns skill.

Related in tooling-ops: build · dx · observability · security · ota