How to Build Reliable Android Automated Tests: From Unit to UI
This article explains why Android testing is crucial, outlines common testability problems, and provides step‑by‑step solutions using MVP architecture, interface‑based design, JUnit, Mockito, Robolectric, Espresso, and CI integration to create maintainable unit and UI tests.
Testing is a critical part of Android development. Early on, most tests are manual, but as app complexity and release speed increase, manual testing becomes a bottleneck. In early 2019, the Huajiao Android team began exploring automated testing and documented the challenges and solutions they encountered.
Improving Testability
The main obstacles were high code coupling (most code resides in Activities/Fragments) and complex class dependencies that rely on concrete implementations, making it hard to create stable test environments.
Extract business logic using MVP and a clean Architecture, moving pure logic into simple Java/Kotlin classes.
Depend on interfaces rather than concrete classes, allowing easy substitution with mock implementations.
Eliminate hidden dependencies: avoid creating new objects inside methods; instead, abstract object creation behind interfaces and inject them via constructors.
Unit Testing
Unit tests catch bugs early and improve code design, encouraging high cohesion and low coupling. The primary tool is JUnit.
Add the JUnit dependency: testImplementation 'junit:junit:4.12' Place test code under src/test/java (or src/test/kotlin for Kotlin). Configure source sets in build.gradle:
sourceSets {
main {
java {
srcDirs = ['src/main/java', 'src/main/kotlin']
}
}
androidTest {
java {
srcDirs = ['src/test/java', 'src/test/kotlin', 'src/androidTest/java', 'src/androidTest/kotlin']
}
}
}Typical unit‑test workflow with JUnit:
Create the object under test, keeping constructors simple and fully initializing the object.
Invoke a single method that should produce only one kind of effect (return value, state change, or interaction with a dependency).
Use assert statements to verify the expected outcome. For state changes, expose getters; for interactions, use Mockito to mock dependencies and verify calls.
Mockito
Mockito helps mock irrelevant dependencies, provide stable behavior, and verify interactions.
// Example using annotations
@RunWith(org.mockito.junit.MockitoJUnitRunner::class)
class SomeTester {
@get:Rule
var rule = MockitoJUnit.rule()
}
class SomeTester {
fun test() {
// Directly create a mock object
val author: Bean = Mockito.mock(Bean::class.java)
}
}Robolectric
Standard unit tests run on the JVM and lack a real Android runtime, leading to RuntimeException("Stub!") errors because android.jar is only a stub. Robolectric provides a local Android sandbox that implements the Android API, allowing tests to run with RobolectricTestRunner without those exceptions. However, unit tests should focus on application logic, leaving platform‑specific tests to integration testing with frameworks like Espresso.
Test Reports and CI Integration
Automated tests can be executed repeatedly, making them ideal for CI pipelines. In Jenkins, configure a Gradle test task. If the project uses product flavors, use the corresponding flavor task name. After the test task finishes, reports are generated under /build/test-results/testDebugUnitTest/ and can be published via Jenkins' "Publish JUnit test report" feature.
When multiple flavors exist, ensure the correct flavor’s test task is used. Jenkins then displays the test results alongside the build.
Integration Testing
Integration tests reside in src/androidTest and share the same package structure as the main source. When run, Android Studio builds two APKs: the regular app APK and a test APK containing assets, resources, and a separate AndroidManifest.xml. Execute tests with:
adb shell am instrument -w <test_package_name>/<runner_class>Typical values: test_package_name is the app’s package, and runner_class is often androidx.test.runner.AndroidJUnitRunner. Within integration tests you can access both the test APK’s and the app APK’s resources via InstrumentationRegistry.getContext() and InstrumentationRegistry.getTargetContext(). Corresponding R classes are com.xxx.test.R and com.xxx.R.
Espresso UI Testing
Espresso, Google’s UI testing framework, lets tests interact with the app from a user’s perspective.
Launch the activity using ActivityTestRule (or a custom rule).
// Launch MainActivity
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)
class DemoRule : ActivityTestRule<FragmentDemoActivity>(FragmentDemoActivity::class.java) {
override fun beforeActivityLaunched() { /* prepare resources */ }
override fun getActivityIntent(): Intent { /* customize intent */ }
override fun afterActivityFinished() { /* cleanup */ }
}Find a view and perform actions: onView(withId(R.id.group_money)) Espresso’s built‑in matchers lack direct support for RecyclerView child views, so Huajiao created a custom RecyclerViewMatcher:
public boolean matchesSafely(View view) {
this.resources = view.getResources();
if (childView == null) {
RecyclerView recyclerView = (RecyclerView) view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
} else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}After locating a view, perform actions (click, double‑click, swipe, etc.) and verify results using ViewInteraction.check with matchers like isDisplayed(), withText(), or custom assertions combined with JUnit asserts.
Conclusion
Android automated testing has historically been overlooked due to tool maturity, rapid app iteration, and learning costs. As app complexity grows and testing frameworks mature, more companies invest in automation. This article shared Huajiao’s practical experience with unit, UI, and integration testing, and future work will expand coverage and adopt additional testing strategies.
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.
Huajiao Technology
The Huajiao Technology channel shares the latest Huajiao app tech on an irregular basis, offering a learning and exchange platform for tech enthusiasts.
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.
