Skip to content

Testing

An app built with SymbioteNative is tested the same way as any other React Native app — SymbioteNative doesn’t ask for a different tool. Two layers: headless component/integration tests, and real-device end-to-end journeys.

Component and integration tests run against a fake Fabric slot — no simulator, device, or native build. Production-critical ESM/CommonJS tools use Node’s built-in test runner so they exercise the exact module format shipped and run without a Vite transform:

Terminal window
pnpm test # both layers
pnpm test:vitest # TypeScript component/integration suites
pnpm test:node # native .test.mjs/.test.cjs tooling suites

Vitest tests live next to what they exercise (Component.test.ts(x) beside Component.ts(x)) across core/, adapters/, and packages/. The Node runner discovers every *.test.mjs and *.test.cjs under scripts/ and packages/, excluding generated build and dependency trees. Adding such a suite therefore places it under the same root pnpm test command CI runs.

Real-device (or simulator) tests drive an actual build of the app:

Terminal window
pnpm -C examples/react e2e:build:ios && pnpm -C examples/react e2e:test:ios
pnpm -C examples/react e2e:build:android && pnpm -C examples/react e2e:test:android

The e2e:* scripts live in each example’s own package.json (examples/react, examples/vue-sfc, examples/vue-tsx, examples/angular, examples/svelte), not the repo root.

Detox’s default synchronization waits for the app to go idle before running the next step — but an app with a perpetual native Animated.loop (a heartbeat pulse, a spinner) never reports idle, so device.launchApp() hangs. Disable synchronization for that launch:

await device.launchApp({
newInstance: true,
launchArgs: { detoxEnableSynchronization: 0 },
});

You then drive individual assertions with explicit waitFor(...) calls instead of relying on Detox’s automatic idle detection.