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 — Vitest

Section titled “Component and integration tests — Vitest”

Unit and integration tests run headless, against a fake Fabric slot — no simulator, no device, no build step:

Terminal window
pnpm test # vitest run

Tests live next to what they exercise (Component.test.ts(x) beside Component.ts(x)), the same co-location convention across core/, adapters/, and the example apps. This is the layer for reducer logic, render-function output, and adapter-lifecycle behavior — anything that doesn’t need a real native host to observe.

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), 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.