The Testing Pyramid

In mobile development, the "Testing Pyramid" is a concept that guides the distribution of your tests. It consists of three layers:

  • Base (70%): Unit Tests. Fast, isolated tests for individual functions.
  • Middle (20%): Integration Tests. Verifying that modules work together (e.g., Database + Repository).
  • Top (10%): UI / E2E Tests. Slow, realistic tests that simulate user interaction on a device.

Inverting this pyramid (having mostly UI tests) leads to "flaky" test suites that take hours to run and are hard to debug.

Unit Testing

Unit tests check the smallest unit of code. In MVVM architecture, you heavily test your ViewModels.

Tools: JUnit & MockK (Android), XCTest (iOS), Jesst (React Native).

Best Practice: Use Dependency Injection to pass mock objects into your classes. If a class instantiates a NetworkClient inside itself, it's hard to test. Pass the client in the constructor instead.

Integration Testing

Mobile apps rely heavily on databases and APIs. Integration tests ensure your data layer parses JSON correctly or that your SQL queries return the right rows.

For Android, Robolectric allows you to run Android-dependent code on the JVM (fast). On iOS, you can stick to XCTest but target non-UI components.

UI Testing

UI tests effectively script a robot to use your app: "Tap button A, wait 2 seconds, assert text B is visible."

  • Espresso (Android): Fast, reliable, runs inside the app process.
  • XCUITest (iOS): Powerful but runs in a separate process, making it slightly slower.
  • Maestro / Detox: Excellent for React Native and Flutter cross-platform testing.

Beta Testing Programs

No amount of automated code can replicate the chaos of the real world. Beta testing helps catch device-specific crashes and usability issues.

  • TestFlight (iOS): The gold standard. Invite up to 10,000 external testers using just their email.
  • Google Play Console Internal/Open Testing: define tracks for internal team, alpha testers, and public beta.

Continuous Integration (CI)

Manual testing is unsustainable. A CI pipeline (Github Actions, Bitrise, CircleCI) automatically builds your app and runs all tests whenever you push code.

Ideal Flow:

  1. Dev pushes code to a Pull Request.
  2. CI runs Lint checks and Unit Tests.
  3. If passed, CI builds a debug APK/IPA.
  4. Reviewer merges PR.
  5. CI builds Release Candidate and uploads to TestFlight/Play Beta.

Test Coverage

Code coverage tells you what percentage of your codebase is executed during tests. While 100% is unrealistic, aim for:

  • Domain/Business Logic: Nearly 100%.
  • UI/View Layer: Lower is fine (UI changes often).

Tools like JaCoCo (Java/Kotlin) or Xcode's coverage tab help visualize untouched code paths.