Espresso basics

This document explains how to complete common automated testing tasks using the Espresso API.

The Espresso API encourages test authors to think in terms of what a user might do while interacting with the application - locating UI elements and interacting with them. At the same time, the framework prevents direct access to activities and views of the application because holding on to these objects and operating on them off the UI thread is a major source of test flakiness. Thus, you will not see methods like getView() and getCurrentActivity() in the Espresso API. You can still safely operate on views by implementing your own subclasses of ViewAction and ViewAssertion.

API components

The main components of Espresso include the following:

  • Espresso – Entry point to interactions with views (via onView() and onData()). Also exposes APIs that are not necessarily tied to any view, such as pressBack().
  • ViewMatchers – A collection of objects that implement the Matcher<? super View> interface. You can pass one or more of these to the onView() method to locate a view within the current view hierarchy.
  • ViewActions – A collection of ViewAction objects that can be passed to the ViewInteraction.perform() method, such as click().
  • ViewAssertions – A collection of ViewAssertion objects that can be passed the ViewInteraction.check() method. Most of the time, you will use the matches assertion, which uses a View matcher to assert the state of the currently selected view.

Example:

Kotlin

// withId(R.id.my_view) is a ViewMatcher
// click() is a ViewAction
// matches(isDisplayed()) is a ViewAssertion
onView(withId(R.id.my_view))
    .perform(click())
    .check(matches(isDisplayed()))

Java

// withId(R.id.my_view) is a ViewMatcher
// click() is a ViewAction
// matches(isDisplayed()) is a ViewAssertion
onView(withId(R.id.my_view))
    .perform(click())
    .check(matches(isDisplayed()));

Find a view

In the vast majority of cases, the onView() method takes a hamcrest matcher that is expected to match one — and only one — view within the current view hierarchy. Matchers are powerful and will be familiar to those who have used them with Mockito or JUnit. If you are not familiar with hamcrest matchers, we suggest you start with a quick look at this presentation.

Often the desired view has a unique R.id and a simple withId matcher will narrow down the view search. However, there are many legitimate cases when you cannot determine R.id at test development time. For example, the specific view may not have an R.id or the R.id is not unique. This can make normal instrumentation tests brittle and complicated to write because the normal way to access the view—with findViewById()— does not work. Thus, you may need to access private members of the Activity or Fragment holding the view or find a container with a known R.id and navigate to its content for the particular view.

Espresso handles this problem cleanly by allowing you to narrow down the view using either existing ViewMatcher objects or your own custom ones.

Finding a view by its R.id is as simple as calling onView():

Kotlin

onView(withId(R.id.my_view))

Java

onView(withId(R.id.my_view));

Sometimes, R.id values are shared between multiple views. When this happens an attempt to use a particular R.id gives you an exception, such as AmbiguousViewMatcherException. The exception message provides you with a text representation of the current view hierarchy, which you can search for and find the views that match the non-unique R.id:

java.lang.RuntimeException:
androidx.test.espresso.AmbiguousViewMatcherException
This matcher matches multiple views in the hierarchy: (withId: is <123456789>)

...

+----->SomeView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=false, enabled=true,
selected=false, is-layout-requested=false, text=,
root-is-layout-requested=false, x=0.0, y=625.0, child-count=1}
****MATCHES****
|
+------>OtherView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=true, enabled=true,
selected=false, is-layout-requested=false, text=Hello!,
root-is-layout-requested=false, x=0.0, y=0.0, child-count=1}
****MATCHES****

Looking through the various attributes of the views, you may find uniquely identifiable properties. In the example above, one of the views has the text "Hello!". You can use this to narrow down your search by using combination matchers:

Kotlin

onView(allOf(withId(R.id.my_view), withText("Hello!")))

Java

onView(allOf(withId(R.id.my_view), withText("Hello!")));

You can also choose not to reverse any of the matchers:

Kotlin

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))

Java

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));

See ViewMatchers for the view matchers provided by Espresso.

Considerations

  • In a well-behaved application, all views that a user can interact with should either contain descriptive text or have a content description. See Making apps more accessible for more details. If you are not able to narrow down a search using withText() or withContentDescription(), consider treating it as an accessibility bug.
  • Use the least descriptive matcher that finds the one view you’re looking for. Do not over-specify as this will force the framework to do more work than is necessary. For example, if a view is uniquely identifiable by its text, you need not specify that the view is also assignable from TextView. For a lot of views the R.id of the view should be sufficient.
  • If the target view is inside an AdapterView—such as ListView, GridView, or Spinner—the onView() method might not work. In these cases, you should use onData() instead.

Perform an action on a view

When you have found a suitable matcher for the target view, it is possible to perform instances of ViewAction on it using the perform method.

For example, to click on the view:

Kotlin

onView(...).perform(click())

Java

onView(...).perform(click());

You can execute more than one action with one perform call:

Kotlin

onView(...).perform(typeText("Hello"), click())

Java

onView(...).perform(typeText("Hello"), click());

If the view you are working with is located inside a ScrollView (vertical or horizontal), consider preceding actions that require the view to be displayed—such as click() and typeText()—with scrollTo(). This ensures that the view is displayed before proceeding to the other action:

Kotlin

onView(...).perform(scrollTo(), click())

Java

onView(...).perform(scrollTo(), click());

See ViewActions for the view actions provided by Espresso.

Check view assertions

Assertions can be applied to the currently selected view with the check() method. The most used assertion is the matches() assertion. It uses a ViewMatcher object to assert the state of the currently selected view.

For example, to check that a view has the text "Hello!":

Kotlin

onView(...).check(matches(withText("Hello!")))

Java

onView(...).check(matches(withText("Hello!")));

If you want to assert that "Hello!" is content of the view, the following is considered bad practice:

Kotlin

// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))

Java

// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));

On the other hand, if you want to assert that a view with the text "Hello!" is visible—for example after a change of the views visibility flag—the code is fine.

View assertion simple test

In this example, SimpleActivity contains a Button and a TextView. When the button is clicked, the content of the TextView changes to "Hello Espresso!".

Here’s how to test this with Espresso:

Click on the button

The first step is to look for a property that helps to find the button. The button in the SimpleActivity has a unique R.id, as expected.

Kotlin

onView(withId(R.id.button_simple))

Java

onView(withId(R.id.button_simple));

Now to perform the click:

Kotlin

onView(withId(R.id.button_simple)).perform(click())

Java

onView(withId(R.id.button_simple)).perform(click());

Verify the TextView text

The TextView with the text to verify has a unique R.id too:

Kotlin

onView(withId(R.id.text_simple))

Java

onView(withId(R.id.text_simple));

Now to verify the content text:

Kotlin

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))

Java

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));

Check data loading in adapter views

AdapterView is a special type of widget that loads its data dynamically from an Adapter. The most common example of an AdapterView is ListView. As opposed to static widgets like LinearLayout, only a subset of the AdapterView children may be loaded into the current view hierarchy. A simple onView() search would not find views that are not currently loaded.

Espresso handles this by providing a separate onData() entry point which is able to first load the adapter item in question, bringing it into focus prior to operating on it or any of its children.

Warning: Custom implementations of AdapterView can have problems with the onData() method if they break inheritance contracts, particularly the getItem() API. In such cases, the best course of action is to refactor your application code. If you cannot do so, you can implement a matching custom AdapterViewProtocol. For more information, take a look at the default AdapterViewProtocols class provided by Espresso.

Adapter view simple test

This simple test demonstrates how to use onData(). SimpleActivity contains a Spinner with a few items that represent types of coffee beverages. When an item is selected, there is a TextView that changes to "One %s a day!", where %s represents the selected item.

The goal of this test is to open the Spinner, select a specific item, and verify that the TextView contains the item. As the Spinner class is based onAdapterView, it is recommended to use onData() instead of onView() for matching the item.

Open the item selection

Kotlin

onView(withId(R.id.spinner_simple)).perform(click())

Java

onView(withId(R.id.spinner_simple)).perform(click());

Select an item

For the item selection, the Spinner creates a ListView with its contents. This view can be very long, and the element might not be contributed to the view hierarchy. By using onData() we force our desired element into the view hierarchy. The items in the Spinner are strings, so we want to match an item that is equal to the String "Americano":

Kotlin

onData(allOf(`is`(instanceOf(String::class.java)),
        `is`("Americano"))).perform(click())

Java

onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());

Verify text is correct

Kotlin

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))))

Java

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))));

Debugging

Espresso provides useful debugging information when a test fails:

Logging

Espresso logs all view actions to logcat. For example:

ViewInteraction: Performing 'single click' action on view with text: Espresso

View hierarchy

Espresso prints the view hierarchy in the exception message when onView() fails.

  • If onView() does not find the target view, a NoMatchingViewException is thrown. You can examine the view hierarchy in the exception string to analyze why the matcher did not match any views.
  • If onView() finds multiple views that match the given matcher, an AmbiguousViewMatcherException is thrown. The view hierarchy is printed and all views that were matched are marked with the MATCHES label:
java.lang.RuntimeException:
androidx.test.espresso.AmbiguousViewMatcherException
This matcher matches multiple views in the hierarchy: (withId: is <123456789>)

...

+----->SomeView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=false, enabled=true,
selected=false, is-layout-requested=false, text=,
root-is-layout-requested=false, x=0.0, y=625.0, child-count=1}
****MATCHES****
|
+------>OtherView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=true, enabled=true,
selected=false, is-layout-requested=false, text=Hello!,
root-is-layout-requested=false, x=0.0, y=0.0, child-count=1}
****MATCHES****

When dealing with a complicated view hierarchy or unexpected behavior of widgets it is always helpful to use the Hierarchy Viewer in Android Studio for an explanation.

Adapter view warnings

Espresso warns users about presence of AdapterView widgets. When an onView() operation throws a NoMatchingViewException and AdapterView widgets are present in the view hierarchy, the most common solution is to use onData(). The exception message will include a warning with a list of the adapter views. You may use this information to invoke onData() to load the target view.

Additional resources

For more information about using Espresso in Android tests, consult the following resources.

Samples