How to Build a Full‑Coverage Android UI Test Platform with Espresso
Despite widespread reliance on manual UI testing, this article details how the author’s team built an Espresso‑based test platform that achieves 80‑90% functional coverage, introduces the ETP design, explains single‑page testing, test flows, and shares practical solutions to common Espresso pitfalls.
Espresso Overview
Espresso is Google’s open‑source Android UI testing framework, released in 2013. It integrates with JUnit and allows developers and testers to write concise UI tests that can cover 80‑90% of functional scenarios.
Espresso Test Platform (ETP) Design
The team built a custom test platform called ETP (Espresso Test Platform) to improve flexibility and reusability. Instead of writing one monolithic test per user flow, tests are split into independent single‑page units that can be combined into larger test flows.
Single‑Page Testing
A single‑page test targets a single Activity or Fragment and validates only the logic of that page. Navigation checks are allowed, but downstream interactions are not exercised. Because Espresso runs on JUnit, multiple independent tests for the same page can be executed in random order.
To achieve full coverage, each single‑page test must contain branches for all possible app states (e.g., logged‑in vs. logged‑out) within the same test file.
Page Association Testing
Test subscription actions on the subscription page and verify success on that page.
On the subscription list page, simulate a successful subscription notification and verify that the new item appears.
Activity with Multiple Fragments
Although Espresso treats an Activity as the entry point, each Fragment is treated as an independent single‑page test. For an app with three tabs plus a main activity, four single‑page tests are defined.
Random Trigger Logic
Global broadcasts may trigger unrelated pop‑ups that interrupt tests. The solution is to write dedicated tests for such pop‑ups and control global flags to disable them during other tests.
Test Flow and Full‑Feature Coverage
After all single‑page tests are written, they are sequenced into test flows . A flow is an ordered execution where the output of one page becomes the input for the next (e.g., login before accessing protected pages).
A test flow strings together different single‑page tests.
One flow can represent a complete business process, covering multiple test cases.
Multiple flows test different logical branches of the same page.
Overlaying flows ensures every page’s logic is exercised, achieving near‑complete functional coverage.
Espresso Pitfalls and Workarounds
IdlingResource Requires a Following UI Action
IdlingResource callbacks are only triggered when a UI operation follows the asynchronous task. If the background work finishes and the screen is closed without a subsequent onView call, the IdlingResource will never report idle, making such scenarios untestable.
Thread‑Specific IdlingResource Bug
Espresso runs on a test thread distinct from the app’s main thread. Code that must execute on the UI thread must be wrapped with runOnUiThread. In some cases the IdlingResource receives the completion callback on the app thread but fails to notify the test thread, causing missed idle notifications.
mActivityTestRule.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
LogUtils.d(TAG, "runOnUiThread..." + Thread.currentThread());
TaskApi.Companion.getMyTasks(0, 10000, "", new HSAPICallback<TaskListResult>() {
@Override
public void onRequestSuccess(TaskListResult data, int httpStatus, Boolean fromCache) {
super.onRequestSuccess(data, httpStatus, fromCache);
mTasks = data.getDatas();
}
});
}
});hasProperty Exception
Hamcrest’s hasProperty matcher fails on Android because the SDK lacks the full JavaBeans library, resulting in NoClassDefFoundError: java/beans/Introspector. The team resolved this by implementing a custom matcher instead of using hasProperty.
DrawerLayout Timing Issue
DrawerLayout introduces a 160 ms delay before its peek animation. Espresso’s perform(click()) sends down/up events asynchronously, which can exceed this window, causing the click to be ignored. The workaround disables the delayed callback via an AOP pointcut that removes the pending runnable.
@Pointcut("execution(* android.support.v4.widget.DrawerLayout.ViewDragCallback.onEdgeTouched(..))")
public void edgeTouchedPoint() {}
@After("edgeTouchedPoint()")
public void onExecutionPoint(final JoinPoint joinPoint) throws Throwable {
Reflect.on(joinPoint.getTarget()).call("removeCallbacks");
}Conclusion
Extensive experimentation shows that Espresso, when combined with the single‑page and test‑flow methodology, can achieve near‑complete functional coverage. The approach has already uncovered numerous bugs caused by careless development, improving test efficiency and product quality.
References
https://google.github.io/android-testing-support-library/docs/espresso/index.html
http://stackoverflow.com/questions/33120493/espresso-idling-resource-doesnt-work
http://baiduhix.blogspot.com/2015/07/android-espresso-ondata-error.html
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.
Hujiang Technology
We focus on the real-world challenges developers face, delivering authentic, practical content and a direct platform for technical networking among developers.
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.
