Guide Area

Android Espresso examples for UI testing

Espresso is a handy native Android tool for UI testing in Android. There is a lot of things you can test. Even though community support is pretty nice, it is sometimes time-consuming to find examples on testing your specific user cases. Therefore, I wrote this article to help you get an overview on some of the methods that Espresso supports. Android Developer documentation provides nice articles about the tests with some examples (more info here: https://developer.android.com/training/testing/ui-testing/espresso-testing.html). The picture below comes from developer.android.com and shows the methods supported by Espresso (click to zoom).

Android Espresso cheat sheet

Espresso works differently when testing position assertions methods than you might think. I describe the process of checking at the end of my article, you should give it a read before testing on your own.

Create project and add gradle dependencies

Create a new project with empty activity. In order to access espresso features, you need to add its dependencies. You do it by opening your app’s build.gradle file and adding following lines:

androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
    exclude module: 'espresso-core'
    exclude module: 'support-v4'
    exclude module: 'recyclerview-v7'
    exclude module: 'appcompat-v7'
    exclude module: 'support-annotations'
    exclude module: 'design'
}
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
    exclude module: 'rules'
    exclude module: 'javax.annotation-api'
    exclude module: 'support-annotations'
}
androidTestCompile('com.android.support.test.espresso:espresso-intents:2.2.2') {
    exclude module: 'rules'
    exclude module: 'javax.annotation-api'
    exclude module: 'support-annotations'
}
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test:rules:0.5'

// add this for intent mocking support
androidTestCompile 'com.android.support.test.espresso:espresso-intents:2.2.2'

// add this for webview testing support
androidTestCompile 'com.android.support.test.espresso:espresso-web:2.2.2'

androidTestCompile 'com.android.support:support-annotations:25.3.1'
testCompile 'junit:junit:4.12'

This configuration also prevents the gradle build error (stacktrace below) which gives a hard time to a lot of Espresso begginers.

Warning:Conflict with dependency 'com.android.support:support-annotations'. Resolved versions for app (...) and test app (...) differ.

Initialize test class

Before you can write test, you have to create a class under app/src/androidTest/java/com/example/myapp.  Your class path could be different, so replace the last 3 locations from the URI accordingly. In your Android Studio, the UI test folder comes with name androidTest in brackets, as shown on the picture below.

Android UI test

Create a new Java class with your desired name (in this tutorial it will be MyTest) and initialize it by inserting this code:

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class MyTest {

    @Rule
    public IntentsTestRule mActivityRule =
            new IntentsTestRule<>(MainActivity.class);

}

MainActivity.class refers to a class which will use your tests to run with. If you want to test UI elements of another class, refactor the parameter of the IntentsTestRule.

Probably the nicest thing about Espresso is that you do not need to inflate XML layouts. IntentsTestRule gives the testing class an idea about what activity will be tested and the specific activity has its own XML layout(s). Espresso will work with these layouts without any need to point them out.

Espresso RecyclerView with scrollToPosition()

This test method checks if – when scrolled to the last position in my RecyclerView, the text “This is the sixth row” is visible on screen (in my case the text is there, so the test passes).

@Test
public void scrollRecyclerView() {
   // scrollTo() is not supported with RecyclerView, but elements like ListView.
   // Method scrollToPosition() from espresso-contrib library works the same way.
   // True test - if scrolled down, text is visible
   onView(withId(R.id.recycler_view)).perform(scrollToPosition(5)).check(matches(hasDescendant(withText("This is the sixth row"))));
   // False test - if scrolled at position 0 (no scroll), text not visible
   // onView(withId(R.id.recycler_view)).perform(scrollToPosition(0)).check(matches(hasDescendant(withText("This is the sixth row"))));
}

Espresso New Intent with categories, flags and extras

When you want to check if new intent was started and parameters were passed along to this intent, you can use the test method below as a guideline. Let’s say we have a button like this one:

Button btn_intent = (Button) findViewById(R.id.btn_intent);
btn_intent.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent n = new Intent(MainActivity.this,OtherActivity.class);
        n.addCategory(Intent.CATEGORY_INFO);
        n.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
        n.putExtra("Activated", true);
        startActivity(n);
    }
});

You can test this by writing a test method such as this one:

@Test
public void ensureIntentStarted() throws InterruptedException {
    onView(withId(R.id.btn_intent)).perform(click());
    intended(
            allOf(
                    hasComponent(OtherActivity.class.getName()),
                    hasCategories(hasItem(equalTo(Intent.CATEGORY_INFO))),
                    hasFlag(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION),
                    hasExtra("Activated",true)
            )
    );
}

If you’d like to make a false test, change hasExtra(“Activated”, true) to hasExtra(“Activated”, false).

Espresso Element to right of other element

@Test
public void ensureRightOf() {
    onView(ViewMatchers.withId(R.id.rightelement)).check(isRightOf(withId(R.id.leftelement)));
}

Espresso Element below other element

@Test
public void ensureBelow() {
    onView(ViewMatchers.withId(R.id.bottomelement)).check(isBelow(withId(R.id.topelement)));
}

Espresso Element displayed on screen

@Test
public void isDisplayed() {
    // True test - showntextview visibility = visible
    onView(withId(R.id.showntextview)).check(matches(ViewMatchers.isDisplayed()));
    // False test - hiddentext not displayed (visibility = gone)
    //onView(withId(R.id.hiddentextview)).check(matches(ViewMatchers.isDisplayed()));
}

Espresso Element isEnabled()

@Test
public void isEnabled() {
    onView(withId(R.id.checkbox)).check(matches(ViewMatchers.isEnabled()));
}

Espresso Element isChecked()

@Test
public void isChecked() {
    // True test - checkbox checked manually with perform(click())
    onView(withId(R.id.checkbox)).perform(click()).check(matches(ViewMatchers.isChecked()));
    // False test - checkbox not checked
    //onView(withId(R.id.checkbox)).check(matches(ViewMatchers.isChecked()));
}

Espresso Element isFocusable()

@Test
public void isFocusable() {
    // True test - edittext is always focusable by default
    onView(withId(R.id.edittext)).check(matches(ViewMatchers.isFocusable()));
    // False test - textview focusable set to false in XML (focusable = false, focusableInTouchMode = false)
    //onView(withId(R.id.textnotfocusable)).check(matches(ViewMatchers.isFocusable()));
}

Description on how Espresso tests position assertions

In order to describe Espresso’s way of tests, let’s take method isRightOf() as an example. I bet, when you first time heard/read about this method, the first thing that came across your mind was RelativeLayout’s alignRightOf property. It’s not wrong to assume that these two would have something in common, but it’s not really like that. Espresso compares positioning by comparing their pixel-to-pixel positions, rather than XML relational positions. The first picture shows a sample application.

Espresso UI Positioning Logic

As you can see by looking at arrows on this design, this layout is a RelativeLayout. The checkbox is positioned toRightOf edittext and they are both positioned above button with label ‘New intent’. Now at this point it is true to say that checkbox-rightof-edittext is true, as well as checkbox-rightof-button. Why is the second statement true?

Espresso does not care about XML positioning. It checks if the condition is true for elements that are rendered on the screen. Picture below shows the left boundary for the checkbox.

Espresso UI Positioning Logic

You can see that neither edittext, nor button crosses the position of checkbox after rendering (displaying on screen). That’s what is important for Espresso to know and that’s why the second statement from above is true, too. If you’d like to see a false test, you can take recyclerView as an example.

Espresso UI Positioning Logic

The width of recyclerView is shown using the line with arrow ends. It’s position overlaps horizontal position of the checkbox, which can no longer be toRightOf recyclerView. None of the elements on screen can be toRightOf any other element which has a width of 100%, in this case recyclerView. Similar explanation goes for isBelow() and isAbove() assertions, except that you have to switch axes which are compared. With that being said, I hope you now understand the logic behind Espresso. Good luck with testing!

Vladimir Marton

DevOps Engineer focused on cloud infrastructure, automation, CI/CD and programming in Javascript, Python, PHP and SQL. Guidearea is my oldest project where I write articles about programming, marketing, SEO and others.