1. Why Use Espresso?

One of the problems with manual testing is that it can be time-consuming and tedious to perform. For example, to test a login screen (manually) in an Android app, you will have to do the following:

Launch the app.

Navigate to the login screen.

Confirm if the usernameEditText and passwordEditText are visible.

Type the username and password into their respective fields.

Confirm if the login button is also visible, and then click on that login button.

Check if the correct views are displayed when that login was successful or was a failure.

Instead of spending all this time manually testing our app, it would be better to spend more time writing code that makes our app stand out from the rest! And, even though manual testing is tedious and quite slow, it is still error-prone and you might miss some corner cases.

Some of the advantages of automated testing include the following:

Automated tests execute exactly the same test cases every time they are executed.

Developers can quickly spot a problem quickly before it is sent to the QA team.

It can save a lot of time, unlike doing manual testing. By saving time, software engineers and the QA team can instead spend more time on challenging and rewarding tasks.

Higher test coverage is achieved, which leads to a better quality application.

In this tutorial, we’ll learn about Espresso by integrating it into an Android Studio project. We’ll write UI tests for a login screen and a RecyclerView, and we’ll learn about testing intents.

3. Create an Android Studio Project

Fire up your Android Studio 3 and create a new project with an empty activity called MainActivity. Make sure to check Include Kotlin support.

4. Set Up Espresso and AndroidJUnitRunner

After creating a new project, make sure to add the following dependencies from the Android Testing Support Library in your build.gradle (although Android Studio has already included them for us). In this tutorial, we are using the latest Espresso library version 3.0.2 (as of this writing).

An Instrumentation that runs JUnit3 and JUnit4 tests against an Android package (application).

Note that Instrumentation is simply a base class for implementing application instrumentation code.

Turn Off Animation

The synchronisation of Espresso, which doesn’t know how to wait for an animation to finish, can cause some tests to fail—if you allow animation on your test device. To turn off animation on your test device, go to Settings > Developer Options and turn off all the following options under the “Drawing” section:

Window animation scale

Transition animation scale

Animator duration scale

5. Write Your First Test in Espresso

First, we start off testing a Login screen. Here’s how the login flow starts: the user launches the app, and the first screen shown contains a single Login button. When that Login button is clicked, it opens up the LoginActivity screen. This screen contains just two EditTexts (the username and password fields) and a Submit button.

Here’s what our MainActivity layout looks like:

Here’s what our LoginActivity layout looks like:

Let’s now write a test for our MainActivity class. Go to your MainActivity class, move the cursor to the MainActivity name, and press Shift-Control-T. Select Create New Test… in the popup menu.

Press the OK button, and another dialog shows up. Choose the androidTest directory and click the OK button once more. Note that because we are writing an instrumentation test (Android SDK specific tests), the test cases reside in the androidTest/java folder.

Now, Android Studio has successfully created a test class for us. Above the class name, include this annotation: @RunWith(AndroidJUnit4::class).

Note that the @Rule annotation means that this is a JUnit4 test rule. JUnit4 test rules are run before and after every test method (annotated with @Test). In our own scenario, we want to launch MainActivity before every test method and destroy it after.

We also included the @JvmField Kotlin annotation. This simply instructs the compiler not to generate getters and setters for the property and instead to expose it as a simple Java field.

Here are the three major steps in writing an Espresso test:

Look for the widget (e.g. TextView or Button) you want to test.

Perform one or more actions on that widget.

Verify or check to see if that widget is now in a certain state.

The following types of annotations can be applied to the methods used inside the test class.

@BeforeClass: this indicates that the static method this annotation is applied to must be executed once and before all tests in the class. This could be used, for example, to set up a connection to a database.

@Before: indicates that the method this annotation is attached to must be executed before each test method in the class.

@Test: indicates that the method this annotation is attached to should run as a test case.

@After: indicates that the method this annotation is attached to should run after each test method.

@AfterClass: indicates that the method this annotation is attached to should run after all the test methods in the class have been run. Here, we typically close out resources that were opened in @BeforeClass.

Find a View Using onView()

In our MainActivity layout file, we just have one widget—the Login button. Let’s test a scenario where a user will find that button and click on it.

To find widgets in Espresso, we make use of the onView() static method (instead of findViewById()). The parameter type we supply to onView() is a Matcher. Note that the Matcher API doesn’t come from the Android SDK but instead from the Hamcrest Project. Hamcrest’s matcher library is inside the Espresso library we pulled via Gradle.

The onView(withId(R.id.btn_login)) will return a ViewInteraction that is for a View whose ID is R.id.btn_login. In the example above, we used withId() to look for a widget with a given id. Other view matchers we can use are:

withText(): returns a matcher that matches TextView based on its text property value.

withHint(): returns a matcher that matches TextView based on its hint property value.

withTagKey(): returns a matcher that matches View based on tag keys.

withTagValue(): returns a matcher that matches Views based on tag property values.

First, let’s test to see if the button is actually displayed on the screen.

onView(withId(R.id.btn_login)).check(matches(isDisplayed()))

Here, we are just confirming if the button with the given id (R.id.btn_login) is visible to the user, so we use the check() method to confirm if the underlying View has a certain state—in our case, if it is visible.

The matches() static method returns a generic ViewAssertion that asserts that a view exists in the view hierarchy and is matched by the given view matcher. That given view matcher is returned by calling isDisplayed(). As suggested by the method name, isDisplayed() is a matcher that matches Views that are currently displayed on the screen to the user. For example, if we want to check if a button is enabled, we simply pass isEnabled() to matches().

Other popular view matchers we can pass into the matches() method are:

hasFocus(): returns a matcher that matches Views that currently have focus.

isChecked(): returns a matcher that accepts if and only if the view is a CompoundButton (or subtype of) and is in checked state. The opposite of this method is isNotChecked().

isSelected(): returns a matcher that matches Views that are selected.

To run the test, you can click the green triangle beside the method or the class name. Clicking the green triangle beside the class name will run all the test methods in that class, while the one beside a method will run the test only for that method.

Hooray! Our test passed!

Perform Actions on a View

On a ViewInteraction object which is returned by calling onView(), we can simulate actions a user can perform on a widget. For example, we can simulate a click action by simply calling the click() static method inside the ViewActions class. This will return a ViewAction object for us.

We perform a click event by first calling perform(). This method performs the given action(s) on the view selected by the current view matcher. Note that we can pass it a single action or a list of actions (executed in order). Here, we gave it click(). Other possible actions are:

typeText() to imitate typing text into an EditText.

clearText() to simulate clearing text in an EditText.

doubleClick() to simulate double-clicking a View.

longClick() to imitate long-clicking a View.

scrollTo() to simulate scrolling a ScrollView to a particular View that is visible.

swipeLeft() to simulate swiping right to left across the vertical center of a View.

Validate With View Assertions

Let’s complete our test, to validate that the LoginActivity screen is shown whenever the Login button is clicked. Though we have already seen how to use check() on a ViewInteraction, let’s use it again, passing it another ViewAssertion.

In the code above, if the entered username is “chike” and the password is “password”, then the login is successful. For any other input, it’s a failure. Let’s now write an Espresso test for this!

Go to LoginActivity.kt, move the cursor to the LoginActivity name, and press Shift-Control-T. Select Create New Test… in the popup menu. Follow the same process as we did for MainActivity.kt, and click the OK button.

This test class is very similar to our first one. If we run the test, our LoginActivity screen is opened. The username and password are typed into the R.id.et_username and R.id.et_password fields respectively. Next, Espresso will click the Submit button (R.id.btn_submit). It will wait until a View with id R.id.tv_login can be found with text reading Success.

7. Test a RecyclerView

RecyclerViewActions is the class that exposes a set of APIs to operate on a RecyclerView. RecyclerViewActions is part of a separate artifact inside the espresso-contrib artifact, which also should be added to build.gradle:

To click on an item at any position in a RecyclerView, we invoke actionOnItemAtPosition(). We have to give it a type of item. In our case, the item is the ViewHolder class inside our RandomAdapter. This method also takes in two parameters; the first is the position, and the second is the action (ViewActions.click()).

Other RecyclerViewActions that can be performed are:

actionOnHolderItem(): performs a ViewAction on a view matched by viewHolderMatcher. This allows us to match it by what’s contained inside the ViewHolder rather than the position.

scrollToPosition(): returns a ViewAction which scrolls RecyclerView to a position.

Next (once the “add note screen” is open), we will enter our note text and save the note. We don’t need to wait for the new screen to open—Espresso will do this automatically for us. It waits until a View with the id R.id.add_note_title can be found.

8. Test Intents

Espresso makes use of another artifact named espresso-intents for testing intents. This artifact is just another extension to Espresso that focuses on the validation and mocking of Intents. Let’s look at an example.

IntentsTestRule extends ActivityTestRule, so they both have similar behaviours. Here’s what the doc says:

This class is an extension of ActivityTestRule, which initializes Espresso-Intents before each test annotated with Test and releases Espresso-Intents after each test run. The Activity will be terminated after each test and this rule can be used in the same way as ActivityTestRule.

The main differentiating feature is that it has additional functionalities for testing startActivity() and startActivityForResult() with mocks and stubs.

We are now going to test a scenario where a user will click on a button (R.id.btn_select_contact) on the screen to pick a contact from the phone’s contact list.

Here we are using intending() from the espresso-intents library to set up a stub with a mock response for our ACTION_PICK request. Here’s what happens in PickContactActivity.kt when the user clicks the button with id R.id.btn_select_contact to pick a contact.

intending() takes in a Matcher that matches intents for which stubbed response should be provided. In other words, the Matcher identifies which request you’re interested in stubbing. In our own case, we make use of hasAction() (a helper method in IntentMatchers) to find our ACTION_PICK request. We then invoke respondWith(), which sets the result for onActivityResult(). In our case, the result has Activity.RESULT_OK, simulating the user selecting a contact from the list.

We then simulate clicking the select contact button, which calls startActivityForResult(). Note that our stub sent the mock response to onActivityResult().

Finally, we use the intended() helper method to simply validate that the calls to startActivity() and startActivityForResult() were made with the right information.

Conclusion

In this tutorial, you learned how to easily use the Espresso testing framework in your Android Studio project to automate your test workflow.

I highly recommend checking out the official documentation to learn more about writing UI tests with Espresso.