Android UI testing is often overlooked, although most developers know it’s something that they should be doing. It’s like eating your vegetables – it’s not a pleasant experience for some, but it helps keep everything in working order. Some reasons often heard for not adopting an automated UI testing solution for a mobile app may be:
- Added time ($$$) to the duration of the project
- Tests are unreliable
- Tests take a long time to complete
- Delays in response time from remote services could cause tests to timeout and fail unexpectedly
- It’s easier to have a real life QA manually step through the app before releases.
Some of these reasons may have some validity, but once you invest a bit of time with Espresso, you’ll become increasingly more efficient with your UI testing and most of these reasons will be addressed. If you really want to be immersed into the power of Espresso, you may want to consider adopting a Test-driven development approach in your next Android project, which will force you to clearly understand the specification and requirements of each feature.
The basic idea of an Espresso test is that it finds a specified view within your app, simulates user interactions (tapping, user input, etc) on it, and asserts that something happened as a result. Espresso has a fluent API which allows cascading of methods. Because we can chain many methods together, we can write very powerful test conditions.
An example of the fluent API used by Espresso is to provide a Matcher, ViewAction, and ViewAssertion, all in one statement. onView(Matcher)will attempt to find a view that matches a certain criteria. For example, a Matcher may be a view with an id of a certain name. perform(ViewAction)is the action you want to perform on the object returned by the Matcher. check(ViewAssertion) is what you expect to happen you performed the action. This pattern is very common and you’ll see it often when looking for examples on GitHub, etc.
Say Goodbye to Sleep
One nice feature of Espresso is that the UI is synchronized with the actions and assertions of your test. This will alleviate any need for introducing calls to Thread.sleep(X) (where X is a safe estimate, in milliseconds, for how long the test should wait until a remote service call, etc., should be finished). Oftentimes, we’ll put a safe value as X to ensure that we allow enough time for the service call to return, be processed, and presented to the UI. However, all of these seconds add up and can make for a long running test. Also, if there is a blip in the network which causes an unforeseen delay, the test fails.
Because Espresso is a first-party UI testing framework provided by Google’s Android team, there is close integration to the Android lifecycle. This give your tests an inside advantage of knowing when your activities are ready to be acted upon, alleviating the need for those pesky Thread.sleep() calls. In most cases, Espresso can determine when actions are completed before evaluating a ViewAssertion. However, in some cases, you may need to implement a IdlingResource for some third party HTTP client libraries such as Retrofit.
Hamcrest – Match All of the Things!
Hamcrest, which is a library of matchers, is an integral part of Espresso. Hamcrest was originally written for Java in 2007 and because of its popularity, it has since been ported to Python, Ruby, Objective-C. PHP, Erlang, and Swift. Hamcrest makes finding views easy.
Here’s an snippet of code which uses Hamcrest matchers to do the following:
- Find an EditText view with the id “email” and select it
- Type an invalid email string and then close the keyboard
- Clicks a button with the id “submit”
- Verifies that a TextView with the id “inputValidationError” is displayed
- Verifies that a TextView with the id “inputValidationSuccess” is not displayed
@Test public void validation_resultIsIncorrect() { // Type a valid string and click on the button. onView(withId(R.id.editText)) .perform(typeText(INVALID_STRING_TO_BE_TYPED), closeSoftKeyboard()); onView(withId(R.id.button)).perform(click()); // Check that the correct sign is displayed. onView(withId(R.id.inputValidationError)).check(matches(isDisplayed())); onView(withId(R.id.inputValidationSuccess)).check(matches(not(isDisplayed()))); }
The snippet above is one of many Espresso examples that Google provides on their official android-testing Github. You can do much more with Espresso, including custom matchers, combining matchers, custom failure handlers, and matching of objects within ListViews or other adapters using the onData() method. A helpful overview of the many Matchers, ViewActions and ViewAssertions can be found in the official Espresso cheatsheet.
Conclusion
Espresso is very easy to add into your project by quickly adding the required Gradle dependencies. You can write tests and have them running against actual devices or emulators within minutes. The API is small and easy to learn and yet allows room for customization. If you’ve decided that automated testing is an integral part of the development lifecycle of your Android app, Espresso deserves your attention.