Mastering Android Unit Testing: Practical Tips, Tools, and Real-World Cases
This article shares practical experiences and best practices for Android unit testing, covering why testing matters, simple test structures, mocking with Mockito, dependency injection via Dagger2, using Robolectric, CI integration, common pitfalls, and a detailed checkout flow example.
Guest Introduction
Zou Yong (aka Xiao Chuang), a senior Android developer at Mogujie Pay, explains his background and passion for unit testing and TDD.
Why Write Unit Tests?
Unit tests improve code quality, catch bugs early, enhance design, and actually save time despite the perceived overhead of writing and maintaining them.
Learning to write tests and adapting project structure both require initial effort, similar to learning to drive a car.
Tests run faster than full app execution.
Early bug detection reduces debugging time.
Refactoring becomes safer and quicker.
Simple Introduction to Unit Tests
Android offers several testing options (JUnit, Instrumentation, Espresso, etc.). The focus here is on JUnit‑based unit tests that run on the JVM, which are fast and isolated from Android framework dependencies.
A unit test typically consists of three steps: setup: instantiate the class under test and set preconditions. exercise: invoke the method being tested. verify: assert that the result matches expectations.
Testing void methods requires verifying side effects, such as whether a dependent component was called with the correct parameters.
Mock Concept and Mockito
Mocks replace real objects in tests, allowing you to specify return values and verify interactions. Mockito is the chosen framework, though it cannot mock static or final members.
Using Mocks with Dependency Injection
Two approaches are discussed: a dedicated testing flavor with factories, or dependency injection (DI). The article adopts DI using Dagger2, which cleanly separates object creation from usage.
In Dagger2, Module s provide dependencies and Component s expose them to clients. A TestingModule can override production bindings to supply mock implementations.
Robolectric: Solving Android Unit‑Testing Pain Points
Running pure JUnit tests on the JVM cannot use Android classes because they throw RuntimeException("Stub"). Robolectric provides a simulated Android environment on the JVM, enabling fast tests that use Android APIs.
Performance comparison:
Instrumentation testing: dozens of seconds.
Robolectric: ~10 seconds.
Pure JUnit: a few seconds.
The stack used in the project is JUnit4 + Mockito + Dagger2 + Robolectric.
A Concrete Case Study
The article walks through testing a checkout flow:
Launch CheckoutActivity and verify loadCheckoutData() is called.
Test CheckoutModel.loadCheckoutData() for correct API invocation.
Mock API responses to verify event bus posting for success and failure.
Validate UI updates in onCheckoutDataLoaded() for both success and error cases.
All dependencies (model, API, bus) are injected and mocked in tests. Full code is available on GitHub.
What Should Be Tested?
All public methods of models, presenters/view‑models, APIs, utilities.
Business logic inside data classes beyond simple getters/setters.
Custom view functionality (e.g., data binding, simple clicks).
Key activity behavior (view existence, data display, error handling).
CI and Code Coverage
Jenkins runs the Gradle test task on each push, using JaCoCo for coverage. Compatibility between Gradle JaCoCo (v7.1) and Jenkins plugin (≤1.0.19) is crucial.
Pitfalls and Good Practices
Native libraries cannot be loaded in pure JUnit or Robolectric; wrap System.loadLibrary in try‑catch or mock the wrapper.
Avoid static methods, singletons, and global state; prefer DI.
Don’t duplicate tests—test shared validators separately from builders.
Maintain a common test library for helpers, rules, and utilities.
Extract pure‑Java utilities from Android SDK to speed up JVM tests.
Leverage JUnit Rule s for reusable setup/teardown and descriptive failure messages.
Use Android Studio live templates and code generation to accelerate test writing.
Accept imperfect tests early; iterate and improve over time.
Consider BDD frameworks (Spock, Spek) for future enhancements.
Q&A Highlights
Answers cover UI interaction testing with Robolectric, handling legacy code, testing custom views, mocking network conditions, naming conventions for test methods, and strategies for testing native‑library dependent code.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
