Testing Mobile Apps: Our Unit, Snapshot, and End-to-End Strategy

Mobile test suites tend to rot faster than backend ones because simulators are slow, device fragmentation is real, and UI tests are brittle by nature. Our baseline on a typical React Native or Swift project is roughly 70% unit tests, 20% snapshot tests, and 10% end-to-end, weighted toward the layer that's cheapest to run and fastest to give feedback.
Unit tests cover business logic — reducers, view models, API response parsing, date and currency formatting edge cases. We keep these framework-agnostic where possible so they run in milliseconds under Jest or XCTest without spinning up a simulator. On one project this caught a timezone bug in a booking flow that would have shipped silently: a Date object built from a UTC string was rendered in device-local time without conversion, off by up to 13 hours for users in New Zealand.
Snapshot tests catch visual regressions cheaply but generate false positives when used carelessly — font rendering differences between CI runners and local machines burned two days early on before we pinned a specific simulator runtime version and switched to per-pixel diffing with a 0.1% tolerance threshold instead of exact match.
End-to-end tests, run with Detox for React Native or XCUITest/Espresso natively, are reserved for the handful of flows where a failure means real revenue loss: signup, checkout, and login. We run these on every release candidate build, not every PR, because a full E2E suite against real devices in device farms like BrowserStack costs both time — 12 to 18 minutes per run — and money.
The strategy that actually holds up over a year of active development is treating flaky tests as bugs, not noise. We quarantine any test that fails twice without a code change, assign it to be fixed or deleted within a sprint, and track flake rate as a dashboard metric alongside coverage — a suite people don't trust gets ignored, which defeats the purpose entirely.

