Native Testing¶
How to QA the native iOS and Android apps — simulators, real devices, OAuth caveats, and the failures you'll see most often.
For the release workflow see Native Release. For local Android dev environment (Android Studio, keystores, SHA-1) see Local development.
Local development modes¶
There are three ways to run the app locally, in increasing order of fidelity to a production build:
1. Expo Go (web / quick-look only)¶
task dev:frontend starts the Expo dev server on :8081. You can scan the QR with the Expo Go app for the fastest possible loop, but this has hard limits:
- No native modules outside the Expo Go SDK (rules out Google Sign-In, certain camera/payment features).
- No custom URL schemes / deep links.
Useful for UI-only changes. For everything else, use a dev client.
2. iOS Simulator¶
cd frontend
npm run dev # starts Metro
# then press "i" in the Metro terminal, or:
npx expo run:ios --simulator
The first run will install the dev client into the simulator. Subsequent runs reuse it.
If you need a build that more closely matches production (signed, with the production dev client), use EAS:
eas build --profile development-simulator --platform ios
# install the resulting .app onto the running simulator
3. Real device via EAS dev client¶
eas build --profile development --platform ios # or android
Wait for the build to finish, then scan the install QR. Once installed, point the dev client at your local Metro server (the dev client landing page has a field to paste the URL).
This is the only way to test features that need a real device — native push notifications, biometric auth, camera, payments, deep links from third-party apps.
Pointing at different backends¶
Mobile builds bake in the EXPO_PUBLIC_API_URL and EXPO_PUBLIC_WS_URL at build time. To test against a non-production backend:
- Local backend: in a dev-client build with Metro pointing at
http://localhost:8081, the JS bundle is built on the fly and you can override env vars in your shell before running Metro. On Android emulator, usehttp://10.0.2.2:8080instead oflocalhost— the emulator can't see your host'slocalhostdirectly. - Dev backend: build the
previewprofile with a temporary override of the API URL env var.
OAuth client IDs¶
Google Sign-In needs a different OAuth client ID per platform. The mobile app reads three:
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID— needed for the ID token exchange on both platformsEXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID— iOS bundle ID-registeredEXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID— Android package + SHA-1 registered
For production builds these come from eas.json / the EAS Secret store. For local dev builds these come from your shell environment when running Metro / npx expo run:*. If you're testing OAuth on a dev build, make sure you've got the right values exported and they match the keystore SHA-1 of the build you're running. See Local Development for the Android SHA-1 details.
Common issues¶
Metro cache weirdness¶
If a JS change isn't showing up, or you see errors referencing modules that no longer exist:
cd frontend
npx expo start --clear # equivalent: npm run dev -- --clear
That clears Metro's transform cache. If it's still wrong, also rm -rf node_modules && npm install.
Android emulator can't see localhost¶
Symptom: backend requests fail immediately with a network error on the Android emulator, but iOS Simulator works fine.
Fix: use 10.0.2.2 instead of localhost for the API URL. The Android emulator runs its own NAT and localhost resolves to the emulator itself, not your host.
Google Sign-In: DEVELOPER_ERROR on Android¶
Symptom: Google Sign-In on Android fails immediately with DEVELOPER_ERROR (code 10).
Cause: the SHA-1 fingerprint of the keystore used to sign your current build is not registered with the Android OAuth client.
Fix:
- Get the SHA-1 of your debug or EAS keystore.
- Add it to the OAuth client in Google Cloud Console → APIs & Services → Credentials.
- Rebuild the app — the change is read at runtime so a rebuild isn't strictly necessary, but
expo prebuildartifacts may need refreshing.
Details and the keystore commands in Local Development.
Push notifications not arriving on iOS¶
Push only works on a real device with a build that includes the push entitlement and is signed against an APNs key. The iOS Simulator can't receive remote push at all (only foreground simulated ones).
Real-time updates stop arriving¶
Likely the WebSocket connection dropped and isn't reconnecting (or backed off too far). Check the device's network state, then check whether the backend Pod is healthy (see Runbook → Investigate WebSocket flapping). The mobile WS client has the same idle-timeout sensitivity as the web client.
See also¶
- Local Development — Android Studio, SDK, emulator, signing
- Native Release — release flow + EAS profiles
- Secrets — what
EXPO_PUBLIC_*actually means