Android fundamentals 06.1: Espresso for UI testing

1. Welcome

Introduction

As a developer, it's important that you test user interactions within your app to make sure that your users don't encounter unexpected results or have a poor experience with your app.

You can test an app's user interface (UI) manually by running the app and trying the UI. But for a complex app, you couldn't cover all the permutations of user interactions within all the app's functionality. You would also have to repeat these manual tests for different device configurations in an emulator, and on different hardware devices.

When you automate tests of UI interactions, you save time, and your testing is systematic. You can use suites of automated tests to perform all the UI interactions automatically, which makes it easier to run tests for different device configurations. To verify that your app's UI functions correctly, it's a good idea to get into the habit of creating UI tests as you code.

Espresso is a testing framework for Android that makes it easy to write reliable UI tests for an app. The framework, which is part of the Android Support Repository, provides APIs for writing UI tests to simulate user interactions within the app—everything from clicking buttons and navigating views to selecting menu items and entering data.

What you should already know

You should be able to:

  • Create and run apps in Android Studio.
  • Check for the Android Support Repository and install it if necessary.
  • Create and edit UI elements using the layout editor and XML.
  • Access UI elements from your code.
  • Add a click handler to a Button.

What you'll learn

  • How to set up Espresso in your app project.
  • How to write Espresso tests.
  • How to test for user input and check for the correct output.
  • How to find a spinner, click one of its items, and check for the correct output.
  • How to use the Record Espresso Test function in Android Studio.

What you'll do

  • Modify a project to create Espresso tests.
  • Test the app's text input and output.
  • Test clicking a spinner item and check its output.
  • Record an Espresso test of a RecyclerView.

2. App overview

In this practical you modify the TwoActivities project from a previous lesson. You set up Espresso in the project for testing, and then you test the app's functionality.

The TwoActivities app lets a user enter text in a text field and tap the Send button, as shown on the left side of the figure below. In the second Activity, the user views the text they entered, as shown on the right side of the figure below.

Entering Text and Clicking Send (left) - Receiving the Message in the Second Activity (right)

3. Task 1: Set up Espresso in your project

To use Espresso, you must already have the Android Support Repository installed with Android Studio. You may also need to configure Espresso in your project.

In this task you check to see if the repository is installed. If it is not, you will install it.

1.1 Check for the Android Support Repository

  1. Download the TwoActivitiesLifecycle project from an earlier codelab on creating and using an Activity.
  2. Open the project in Android Studio, and choose Tools > Android > SDK Manager.

The Android SDK Default Preferences pane appears.

  1. Click the SDK Tools tab and expand Support Repository.
  2. Look for Android Support Repository in the list.

If Installed appears in the Status column, you're all set. Click Cancel.

If Not installed or Update Available appears, click the checkbox next to Android Support Repository. A download icon should appear next to the checkbox. Click OK.

  1. Click OK again, and then Finish when the support repository has been installed.

1.2 Configure Espresso for the project

When you start a project for the phone and tablet form factor using API 15: Android 4.0.3 (Ice Cream Sandwich) as the minimum SDK, Android Studio automatically includes the dependencies you need to use Espresso. To execute tests, Espresso and UI Automator use JUnit as their testing framework. JUnit is the most popular and widely-used unit testing framework for Java. Your test class using Espresso or UI Automator should be written as a JUnit 4 test class.

If you have created your project in a previous version of Android Studio, you may have to add the dependencies and instrumentation yourself. To add the dependencies yourself, follow these steps:

  1. Open the TwoActivities project, or if you prefer, make a copy of the project first and then open the copy.
  2. Open the build.gradle (Module: app) file.
  3. Check if the following is included (along with other dependencies) in the dependencies section:
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 
           'com.android.support.test.espresso:espresso-core:3.0.1'

If the file doesn't include the above dependency statements, enter them into the dependencies section.

  1. Android Studio also adds the following instrumentation statement to the end of the defaultConfig section of a new project:
testInstrumentationRunner 
                "android.support.test.runner.AndroidJUnitRunner"

If the file doesn't include the above instrumentation statement, enter it at the end of the defaultConfig section.

Instrumentation is a set of control methods, or hooks, in the Android system. These hooks control an Android component independently of the component's normal lifecycle. They also control how Android loads apps. Using instrumentation makes it possible for tests to invoke methods in the app, and modify and examine fields in the app, independently of the app's normal lifecycle.

  1. If you modified the build.gradle (Module: app) file, click the Sync Now link in the notification about Gradle files in top right corner of the window.

1.3 Turn off animations on your test device

To let Android Studio communicate with your device, you must first turn on USB Debugging on your device, as described in the lesson on installing and running apps.

Android phones and tablets display animations when moving between apps and screens. The animations are attractive when using the device, but they slow down performance, and may also cause unexpected results or may lead your test to fail. So it's a good idea to turn off animations on your physical device. To turn off animations on your test device, tap on the Settings icon on your physical device. Look for Developer Options. Now look for the Drawing section. In this section, turn off the following options:

  • Window animation scale
  • Transition animation scale
  • Animator duration scale

4. Task 2: Test for Activity switching and text input

You write Espresso tests based on what a user might do while interacting with your app. The Espresso tests are classes that are separate from your app's code. You can create as many tests as you need in order to interact with the View elements in your UI that you want to test.

The Espresso test is like a robot that must be told what to do. It must find the View you want it to find on the screen, and it must interact with it, such as clicking the View, and checking the contents of the View. If it fails to do any of these things properly, or if the result is not what you expected, the test fails.

With Espresso, you create what is essentially a script of actions to take on each View and check against expected results. The key concepts are locating and then interacting with UI elements. These are the basic steps:

  1. Match a View: Find a View.
  2. Perform an action: Perform a click or other action that triggers an event with the View.
  3. Assert and verify the result: Check the state of the View to see if it reflects the expected state or behavior defined by the assertion.

Hamcrest (an anagram of "matchers") is a framework that assists writing software tests in Java. To create a test, you create a method within the test class that uses Hamcrest expressions.

With Espresso you use the following types of Hamcrest expressions to help find View elements and interact with them:

  • ViewMatchers: Hamcrest matcher expressions in the ViewMatchers class that lets you find a View in the current View hierarchy so that you can examine something or perform some action.
  • ViewActions: Hamcrest action expressions in the ViewActions class that lets you perform an action on a View found by a ViewMatcher.
  • ViewAssertions: Hamcrest assertion expressions in the ViewAssertions class that lets you assert or check the state of a View found by a ViewMatcher.

The following shows how all three expressions work together:

  1. Use a ViewMatcher to find a View: onView(withId(R.id.my_view))
  2. Use a ViewAction to perform an action: .perform(click())
  3. Use a ViewAssertion to check if the result of the action matches an assertion: .check(matches(isDisplayed()))

The following shows how the above expressions are used together in a statement:

onView(withId(R.id.my_view))
        .perform(click())
        .check(matches(isDisplayed())); 

2.1 Run the example test

Android Studio creates a blank Espresso test class when you create the project.

  1. In the Project > Android pane, open java > com.example.android.twoactivities (androidTest), and open ExampleInstrumentedTest.

The project is supplied with this example test. You can create as many tests as you wish in this folder. In the next step you will edit the example test.

  1. In the Project > Android pane, open java > com.example.android.twoactivities (androidTest), and open ExampleInstrumentedTest.
  2. To run the test, right-click (or Control-click) ExampleInstrumentedTest and choose Run ExampleInstrumentedTest from the pop-up menu. You can then choose to run the test on the emulator or on your device.

The Run pane at the bottom of Android Studio shows the progress of the test, and when finishes, it displays "Tests ran to completion." In the left column Android Studio displays "All Tests Passed".

2.2 Define a class for a test and set up the Activity

You will edit the example test rather than add a new one. To make the example test more understandable, you will rename the class from ExampleInstrumentedTest to ActivityInputOutputTest. Follow these steps:

  1. Right-click (or Control-click) ExampleInstrumentedTest in the Project > Android pane, and choose Refactor > Rename.
  2. Change the class name to ActivityInputOutputTest, and leave all options checked. Click Refactor.
  3. Add the following to the top of the ActivityInputOutputTest class, before the first @Test annotation:
@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(
                     MainActivity.class);
}

The class definition now includes several annotations:

  • @RunWith: To create an instrumented JUnit 4 test class, add the @RunWith(AndroidJUnit4.class) annotation at the beginning of your test class definition.
  • @Rule: The @Rule annotation lets you add or redefine the behavior of each test method in a reusable way, using one of the test rule classes that the Android Testing Support Library provides, such as ActivityTestRule or ServiceTestRule. The rule above uses an ActivityTestRule object, which provides functional testing of a single Activity—in this case, MainActivity.class. During the duration of the test you will be able to manipulate your Activity directly, using ViewMatchers, ViewActions, and ViewAssertions.
  • @Test: The @Test annotation tells JUnit that the public void method to which it is attached can be run as a test case. A test method begins with the @Test annotation and contains the code to exercise and verify a single function in the component that you want to test.

In the above statement, ActivityTestRule may turn red at first, but then Android Studio adds the following import statement automatically:

import android.support.test.rule.ActivityTestRule;

2.3 Test switching from one Activity to another

The TwoActivities app includes:

  • MainActivity: Includes the button_main button for switching to SecondActivity and the text_header_reply view that serves as a text heading.
  • SecondActivity: Includes the button_second button for switching to MainActivity and the text_header view that serves as a text heading.

When you have an app that switches from one Activity to another, you should test that capability. The TwoActivities app provides a text entry field and a Send button (the button_main id). Clicking Send launches SecondActivity with the entered text shown in the text_header view of SecondActivity.

But what happens if no text is entered? Will SecondActivity still appear?

The ActivityInputOutputTest class of tests will show that the View elements in SecondActivity appear regardless of whether text is entered.

Follow these steps to add your tests to ActivityInputOutputTest:

  1. Add the beginning of the activityLaunch() method to ActivityInputOutputTest. This method will test whether the SecondActivity View elements appear when clicking the Button, and include the @Test notation on a line immediately above the method:
@Test
public void activityLaunch() {
  1. Add a combined ViewMatcher and ViewAction expression to the activityLaunch() method to locate the View containing the button_main Button, and include a ViewAction expression to perform a click.
onView(withId(R.id.button_main)).perform(click());

The onView() method lets you use ViewMatcher arguments to find View elements. It searches the View hierarchy to locate a corresponding View instance that meets some given criteria—in this case, the button_main View. The .perform(click()) expression is a ViewAction expression that performs a click on the View.

In the above onView statement, onView, withID, and click may turn red at first, but then Android Studio adds import statements for them.

  1. Add another ViewMatcher expression to the activityLaunch() method to find the text_header View (which is in SecondActivity), and a ViewAction expression to perform a check to see if the View is displayed:
onView(withId(R.id.text_header)).check(matches(isDisplayed()));

This statement uses the onView() method to locate the text_header View for SecondActivity and then check to see if it is displayed after clicking the button_main Button. The check() method may turn red at first, but then Android Studio adds an import statement for it.

  1. Add similar statements to test whether clicking the button_second Button in SecondActivity switches to MainActivity:
onView(withId(R.id.button_second)).perform(click());
onView(withId(R.id.text_header_reply)).check(matches(isDisplayed()));
  1. Review the activityLaunch() method you just created in the ActivityInputOutputTest class. It should look like this:
@Test
public void activityLaunch() {
   onView(withId(R.id.button_main)).perform(click());
   onView(withId(R.id.text_header)).check(matches(isDisplayed()));
   onView(withId(R.id.button_second)).perform(click());
   onView(withId(R.id.text_header_reply)).check(matches(isDisplayed()));
}
  1. To run the test, right-click (or Control-click) ActivityInputOutputTest and choose Run ActivityInputOutputTest from the pop-up menu. You can then choose to run the test on the emulator or on your device.

As the test runs, watch the test automatically start the app and click the Button. The SecondActivity View elements appear. The test then clicks the Button in SecondActivity, and MainActivity View elements appear.

The Run window (the bottom pane of Android Studio) shows the progress of the test, and when finishes, it displays "Tests ran to completion." In the left column Android Studio displays "All Tests Passed".

The drop-down menu next to the Run icon or switch to app in the dropdown menu and then click the Run icon to run the app.   [IMAGEINFO]: ic_run.png, Android Studio Run icon in the Android Studio toolbar now shows the name of the test class (ActivityInputOutputTest). You can click the Run icon to run the test, or switch to app in the dropdown menu and then click the Run icon to run the app.

2.4 Test text input and output

In this step you will write a test for text input and output. The TwoActivities app uses the editText_main EditText for input, the button_main Button for sending the input to SecondActivity, and the TextView in SecondActivity that shows the output in the field with the id text_message.

  1. Add another @Test annotation and a new textInputOutput() method to test text input and output:
@Test
public void textInputOutput() {
   onView(withId(R.id.editText_main)).perform(typeText("This is a test."));
   onView(withId(R.id.button_main)).perform(click());
}

The above method uses a ViewMatcher to locate the View containing the editText_main EditText, and a ViewAction to enter the text "This is a test." It then uses another ViewMatcher to find the View with the button_main Button, and another ViewAction to click the Button.

  1. Add a ViewMatcher to the method to locate the text_message TextView in SecondActivity, and a ViewAssertion to see if the output matches the input to test that the message was correctly sent—it should match "This is a test." (Be sure to include the period at the end.)
onView(withId(R.id.text_message))
                       .check(matches(withText("This is a test.")));
  1. Run the test.

As the test runs, the app starts and the text is automatically entered as input; the Button is clicked, and SecondActivity appears with the text.

The bottom pane of Android Studio shows the progress of the test, and when finished, it displays "Tests ran to completion." In the left column Android Studio displays "All Tests Passed". You have successfully tested the EditText input, the Send Button, and the TextView output.

2.5 Introduce an error to show a test failing

Introduce an error in the test to see what a failed test looks like.

  1. Change the matches check on the text_message view from "This is a test." to "This is a failing test.":
onView(withId(R.id.text_message)).check(matches(withText("This is a failing test.")));
  1. Run the test again. This time you will see the message in red, "1 test failed", above the bottom pane, and a red exclamation point next to textInputOutput in the left column. Scroll the bottom pane to the message "Test running started" and see that all of the results after that point are in red. The very next statement after "Test running started" is:
android.support.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'with text: is "This is a failing test."' doesn't match the selected view.
Expected: with text: is "This is a failing test."
Got: "AppCompatTextView{id=2131165307, res-name=text_message ...

Other fatal error messages appear after the above, due to the cascading effect of a failure leading to other failures. You can safely ignore them and fix the test itself.

Task 2 solution code

See ActivityInputOutputTest.java in the Android Studio project: TwoActivitiesEspresso

5. Task 3: Test the display of spinner selections

The Espresso onView() method finds a View that you can test. This method will find a View in the current View hierarchy. But you need to be careful—in an AdapterView such as a Spinner, the View is typically dynamically populated with child View elements at runtime. This means there is a possibility that the View you want to test may not be in the View hierarchy at that time.

The Espresso API handles this problem by providing a separate onData() entry point, which is able to first load the adapter item and bring it into focus prior to locating and performing actions on any of its children.

PhoneNumberSpinner is a starter app you can use to test a Spinner. It shows a Spinner, with the id label_spinner, for choosing the label of a phone number (Home, Work, Mobile, and Other). It then displays the phone number and Spinner choice in a TextView.

The goal of this test is to open the Spinner, click each item, and then verify that the TextView text_phonelabel contains the item. The test demonstrates that the code retrieving the Spinner selection is working properly, and the code displaying the text of the Spinner item is also working properly. You will write the test using string resources and iterate through the Spinner items so that the test works no matter how many items are in the Spinner, or how those items are worded; for example, the words could be in a different language.

Spinner

3.1 Create the test method

  1. Download the PhoneNumberSpinnerEspresso project and then open the project in Android Studio.
  2. Run the app. Enter a phone number, and choose a label from the Spinner as shown on the left side of the figure below. The result should appear in the TextView and in a Toast message, as shown on the right side of the figure.

Entering a phone number and choosing from the Spinner (left), and the result (right).

  1. Expand com.example.android.phonenumberspinner (androidTest), and open ExampleInstrumentedTest.
  2. Rename ExampleInstrumentedTest to SpinnerSelectionTest in the class definition, and add the following:
@RunWith(AndroidJUnit4.class)
public class SpinnerSelectionTest {
   @Rule
   public ActivityTestRule mActivityRule = new ActivityTestRule<>(
                     MainActivity.class);

3.2 Access the array used for the Spinner items

You want the test to click each item in the Spinner based on the number of elements in the array. But how do you access the array?

  1. Create the iterateSpinnerItems() method as public returning void, and assign the array used for the Spinner items to a new array to use within the iterateSpinnerItems() method:
@Test
public void iterateSpinnerItems() {
   String[] myArray = 
         mActivityRule.getActivity().getResources()
         .getStringArray(R.array.labels_array);
}

In the statement above, the test accesses the array (with the id labels_array) by establishing the context with the getActivity() method of the ActivityTestRule class, and getting a resources instance using getResources().

  1. Assign the length of the array to size, and start the beginning of a for loop using the size as the maximum number for a counter.
int size = myArray.length;
    for (int i=0; i<size; i++) {

3.3 Locate Spinner items and click on them

  1. Add an onView() statement within the for loop to find the Spinner and click on it:
// Find the spinner and click on it.
onView(withId(R.id.label_spinner)).perform(click());

A user must click the Spinner itself in order to click any item in the Spinner, so your test must click the Spinner first before clicking the item.

  1. Write an onData() statement to find and click a Spinner item:
// Find the spinner item and click on it.
onData(is(myArray[i])).perform(click());

The above statement matches if the object is a specific item in the Spinner, as specified by the myArray[i] array element.

If onData appears in red, click the word, and then click the red light bulb icon that appears in the left margin. Choose the following in the pop-up menu: Static import method ‘android.support.test.espresso.Espresso.onData'

If is appears in red, click the word, and then click the red light bulb icon that appears in the left margin. Choose the following in the pop-up menu: Static import method...> Matchers.is (org.hamcrest)

  1. Add another onView() statement to the for loop to check to see if the resulting text_phonelabel matches the Spinner item specified by myArray[i].
// Find the text view and check that the spinner item 
// is part of the string.
onView(withId(R.id.text_phonelabel))
                   .check(matches(withText(containsString(myArray[i]))));

If containsString appears in red, click the word, and then click the red light bulb icon that appears in the left margin. Choose the following in the pop-up menu: Static import method...> Matchers.containsString (org.hamcrest)

  1. Run the test.

The test runs the app, clicks the Spinner, and "exercises" the Spinner—it clicks each Spinner item from top to bottom, checking to see if the item appears in the text_phonelabel TextView. It doesn't matter how many Spinner items are defined in the array, or what language is used for the Spinner items—the test performs all of them and checks their output against the array.

The bottom pane of Android Studio shows the progress of the test, and when finished, it displays "Tests ran to completion." In the left column Android Studio displays "All Tests Passed".

Task 3 solution code

See SpinnerSelectionTest.java in the Android Studio project: PhoneNumberSpinnerEspresso

6. Task 4: Record an Espresso test

Android Studio lets you record an Espresso test, which is useful for generating tests quickly. While recording a test, you use your app as a normal user—as you click through the app UI, editable test code is generated for you. You can also add assertions to check if a View holds a certain value.

Recording Espresso tests, rather than coding the tests by hand, ensures that your app gets UI test coverage on areas that might take too much time or be too difficult to code by hand.

To demonstrate test recording, you will record a test of the Scorekeeper app created in the practical on using drawables, styles, and themes.

4.1 Open and run the app

  1. Download the Scorekeeper project that you created in Android fundamentals 5.1: Drawables, styles, and themes.
  2. Open the Scorekeeper project in Android Studio.
  3. Run the app to ensure that it runs properly.

The Scorekeeper app consists of two sets of Button elements and two TextView elements, which are used to keep track of the score for any point-based game with two players.

The Scorekeeper app

4.2 Record the test

  1. Choose Run > Record Espresso Test, select your deployment target (an emulator or a device), and click OK.

The Record Your Test dialog appears, and the Debugger pane appears at the bottom of the Android Studio window. If you are using an emulator, the emulator also appears.

Record Your Test dialog

  1. On the emulator or device, tap the plus (+) ImageButton for Team 1 in the app. The Record Your Test window shows the action that was recorded ("Tap AppCompatImageButton with the content description Plus Button").

Recording an ImageButton Click for an Espresso Test

  1. Click Add Assertion in the Record Your Test window. A screenshot of the app's UI appears in a pane on the right side of the window, and the Select an element from screenshot option appears in the dropdown menu. Select the score (1) in the screenshot as the UI element you want to check, as shown in the figure below.

Adding an assertion about a specific UI element to the recording

  1. Choose text is from the second dropdown menu, as shown in the figure below. The text you expect to see (1) is already entered in the field below the dropdown menu.

Adding an assertion about the value of the UI element to the recording

  1. Click Save Assertion.
  2. To record another click, on your emulator or device tap the minus () ImageButton for Team 1 in the app. The Record Your Test window shows the action that was recorded ("Tap AppCompatImageButton with the content description Minus Button").

Recording another ImageButton Click for an Espresso Test

  1. Click Add Assertion in the Record Your Test window. The app's UI appears in the right-side pane as before. Select the score (0) in the screenshot as the UI element you want to check.

Recording another ImageButton Click for an Espresso Test

  1. Choose text is from the second dropdown menu, as shown in the figure below. The text you expect to see (0) is already entered in the field below the dropdown menu.

Adding an assertion about the value of the UI element to the recording

  1. Click Save Assertion, and then click OK.
  2. In the dialog that appears, edit the name of the test to ScorePlusMinusTest so that it is easy for others to understand the purpose of the test.
  3. Android Studio may display a request to add more dependencies to your Gradle Build file. Click Yes to add the dependencies. Android Studio adds the following to the dependencies section of the build.gradle (Module: app) file:
androidTestCompile
         'com.android.support.test.espresso:espresso-contrib:2.2.2', {
   exclude group: 'com.android.support', module: 'support-annotations'
   exclude group: 'com.android.support', module: 'support-v4'
   exclude group: 'com.android.support', module: 'design'
   exclude group: 'com.android.support', module: 'recyclerview-v7'
}
  1. Expand com.example.android.scorekeeper (androidTest) to see the test, and run the test. It should pass. Run it again, and it should pass again.

The following is the test, as recorded in the ScorePlusMinusTest.java file:

// ... Package and import statements
@LargeTest
@RunWith(AndroidJUnit4.class)
public class ScorePlusMinusTest {

    @Rule
    public ActivityTestRule<MainActivity> mActivityTestRule 
                        = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void scorePlusMinusTest() {
        ViewInteraction appCompatImageButton = onView(
                allOf(withId(R.id.increaseTeam1), 
                withContentDescription("Plus Button"),
                        childAtPosition(
                                childAtPosition(
                withClassName(is("android.widget.LinearLayout")),
                                        0),
                                3),
                        isDisplayed()));
        appCompatImageButton.perform(click());

        ViewInteraction textView = onView(
                allOf(withId(R.id.score_1), withText("1"),
                        childAtPosition(
                                childAtPosition(IsInstanceOf
               .<View>instanceOf(android.widget.LinearLayout.class),
                                        0),
                                2),
                        isDisplayed()));
        textView.check(matches(withText("1")));

        ViewInteraction appCompatImageButton2 = onView(
                allOf(withId(R.id.decreaseTeam1), 
                        withContentDescription("Minus Button"),
                        childAtPosition(
                                childAtPosition(
                withClassName(is("android.widget.LinearLayout")),
                                        0),
                                1),
                        isDisplayed()));
        appCompatImageButton2.perform(click());

        ViewInteraction textView2 = onView(
                allOf(withId(R.id.score_1), withText("0"),
                        childAtPosition(
                                childAtPosition(IsInstanceOf
                .<View>instanceOf(android.widget.LinearLayout.class),
                                        0),
                                2),
                        isDisplayed()));
        textView2.check(matches(withText("0")));

    }

    private static Matcher<View> childAtPosition(
            final Matcher<View> parentMatcher, final int position) {

        return new TypeSafeMatcher<View>() {
            @Override
            public void describeTo(Description description) {
                description.appendText("Child at position " 
                + position + " in parent ");
                parentMatcher.describeTo(description);
            }

            @Override
            public boolean matchesSafely(View view) {
                ViewParent parent = view.getParent();
                return parent instanceof ViewGroup 
                        && parentMatcher.matches(parent)
                        && view.equals(((ViewGroup) parent)
                        .getChildAt(position));
            }
        };
    }
}

The test uses the ViewInteraction class, which is the primary interface for performing actions or assertions on View elements, providing both check() and perform() methods. Examine the test code to see how it works:

  • Perform: The code below uses a method called childAtPosition(), which is defined as a custom Matcher, and the perform() method to click an ImageButton:
ViewInteraction appCompatImageButton = onView(
      allOf(withId(R.id.increaseTeam1), 
              withContentDescription("Plus Button"),
                        childAtPosition(
                                childAtPosition(
              withClassName(is("android.widget.LinearLayout")),
                                        0),
                                3),
                        isDisplayed()));
appCompatImageButton.perform(click());
  • Check whether it matches the assertion: The code below also uses the childAtPosition() custom Matcher, and checks to see if the clicked item matches the assertion that it should be "1":
ViewInteraction textView = onView(
                allOf(withId(R.id.score_1), withText("1"),
                        childAtPosition(
                                childAtPosition(
                                        IsInstanceOf                   
          .<View>instanceOf(android.widget.LinearLayout.class),
                                        0),
                                2),
                        isDisplayed()));
textView.check(matches(withText("1")));
  • Custom Matcher: The code above uses childAtPosition(), which is defined as a custom Matcher:
private static Matcher<View> childAtPosition(
       final Matcher<View> parentMatcher, final int position) {

        return new TypeSafeMatcher<View>() {
            @Override
            public void describeTo(Description description) {
                description.appendText("Child at position "
                        + position + " in parent ");
                parentMatcher.describeTo(description);
            }

            @Override
            public boolean matchesSafely(View view) {
                ViewParent parent = view.getParent();
                return parent instanceof ViewGroup
                        && parentMatcher.matches(parent)
                        && view.equals(((ViewGroup) parent)
                        .getChildAt(position));
            }
        };
}

The custom Matcher in the above code extends the abstract TypeSafeMatcher class.

You can record multiple interactions with the UI in one recording session. You can also record multiple tests, and edit the tests to perform more actions, using the recorded code as a snippet to copy, paste, and edit.

Task 4 solution code

See ScorePlusMinusTest.java in the Android Studio project: ScorekeeperEspresso

7. Coding challenge

You learned how to create a RecyclerView in another practical. The app lets you scroll a list of words from "Word 1" to "Word 19". When you tap the FloatingActionButton, a new word appears in the list ("+ Word 20").

Like an AdapterView (such as a Spinner), a RecyclerView dynamically populates child View elements at runtime. But a RecyclerView is not an AdapterView, so you can't use onData() to interact with list items as you did in the previous task with a Spinner. What makes a RecyclerView complicated from the point of view of Espresso is that onView() can't find the child View if it is off the screen.

Challenge: Fortunately, you can record an Espresso test of using the RecyclerView. Record a test that taps the FloatingActionButton, and check to see if a new word appears in the list ("+ Word 20").

Challenge solution code

See RecyclerViewTest.java in the Android Studio project: RecyclerViewEspresso

8. Summary

To set up Espresso to test an Android Studio project:

  • In Android Studio, check for and install the Android Support Repository.
  • Add dependencies to the build.gradle (Module: app) file:
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 
           'com.android.support.test.espresso:espresso-core:3.0.1'
  • Add the following instrumentation statement to the end of the defaultConfig section:
testInstrumentationRunner 
                "android.support.test.runner.AndroidJUnitRunner"

Instrumentation is a set of control methods, or hooks, in the Android system.

  • On your test device, turn off animations. To do this, turn on USB Debugging. Then in the Settings app, select Developer Options > Drawing. Turn off window animation scale, transition animation scale, and animator duration scale.

To test annotations:

  • @RunWith(AndroidJUnit4.class): Create an instrumented JUnit 4 test class.
  • @Rule: Add or redefine the behavior of each test method in a reusable way, using one of the test rule classes that the Android Testing Support Library provides, such as ActivityTestRule or ServiceTestRule.
  • @Test: The @Test annotation tells JUnit that the public void method to which it is attached can be run as a test case.

To test code:

  • ViewMatchers class lets you find a view in the current View hierarchy so that you can examine something or perform an action.
  • ViewActions class lets you perform an action on a view found by a ViewMatcher.
  • ViewAssertions class lets you assert or check the state of a view found by a ViewMatcher.

To test a spinner:

  • Use onData() with a View that is dynamically populated by an adapter at runtime.
  • Get items from an app's array by establishing the context with getActivity() and getting a resources instance using getResources().
  • Use onData() to find and click each spinner item.
  • Use onView() with a ViewAction and ViewAssertion to check if the output matches the selected spinner item.

9. Related concept

The related concept documentation is in 6.1: UI testing.

10. Learn more

Android Studio documentation:

Android developer documentation:

Videos

Other:

11. Homework

This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:

  • Assign homework if required.
  • Communicate to students how to submit homework assignments.
  • Grade the homework assignments.

Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.

If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.

Build and run an app

Record another Espresso test for the ScorekeeperEspresso app. This test should tap the Night Mode option in the options menu and determine whether the Day Mode option appears in its place. The test should then tap the Day Mode option and determine whether the Night Mode option appears in its place.

Answer these questions

Question 1

Which steps do you perform to test a View interaction, and in what order? Choose one:

  • Match a View, assert and verify the result, and perform an action.
  • Match a View, perform an action, and assert and verify the result.
  • Perform an action, match a view, and assert and verify the result.
  • Perform an action, and assert and verify the result.

Question 2

Which of the following annotations enables an instrumented JUnit 4 test class? Choose one:

  • @RunWith
  • @Rule
  • @Test
  • @RunWith and @Test

Question 3

Which method would you use to find a child View in an AdapterView? Choose one:

  • onData() to load the adapter and enable the child View to appear on the screen.
  • onView() to load the View from the current View hierarchy.
  • onView().check() to check the current View.
  • onView().perform() to perform a click on the current View.

Submit your app for grading

Guidance for graders

Check that the test meets the following criteria:

  • The test appears in the com.example.android.scorekeeper (androidTest) folder.
  • The test automatically switches the app from Day Mode to Night Mode, and then back to Day Mode.
  • The test passes more than once.

12. Next codelab

To find the next practical codelab in the Android Developer Fundamentals (V2) course, see Codelabs for Android Developer Fundamentals (V2).

For an overview of the course, including links to the concept chapters, apps, and slides, see Android Developer Fundamentals (Version 2).