© Denys Zelenchuk 2019
Denys ZelenchukAndroid Espresso Revealedhttps://doi.org/10.1007/978-1-4842-4315-2_1

1. Getting Started with Espresso for Android

Denys Zelenchuk1 
(1)
Zürich, Switzerland
 

Espresso for Android is a lightweight, fast, and customizable Android testing framework, designed to provide concise and reliable automated UI tests. At the end of October 2013, Espresso was open sourced by Google after it was announced at the Google Test Automation Conference. From that moment it has been gaining popularity across Android software and test engineers. Now it is the most popular testing framework for the Android platform because its features and development are driven by Google and the Android Open Source community.

This chapter describes Espresso’s basics—the core components of the Espresso testing framework that are used in test automation to replicate the end user behavior. This includes locating application UI elements on the screen and operating on them.

Espresso includes the following packages:
  • espresso-core—Contains core and basic view matchers, actions, and assertions.

  • espresso-contrib—External contributions that contain DatePicker, RecyclerView, and Drawer actions, accessibility checks, and the CountingIdlingResource.

  • espresso-intents—Extensions to validate and stub intents for hermetic testing.

  • espresso-idling-resource—Espresso’s mechanism for synchronizing background jobs.

  • espresso-remote—Location of Espresso’s multi-process functionality.

  • espresso-web—Contains resources for WebView support.

User Interface Testing: Goals and Approach

As mentioned, this book focuses on writing functional end-to-end UI tests, which is the closest way to replicate end user behavior and catch potential issues before a product goes live. Despite the fact that such tests can be much slower than unit or integration tests, they usually discover issues that were not caught during the unit and integration testing stages.

I would like to emphasize the fact that all the test examples in the book do not contain any conditional logic. Conditional logic in test automation is a bad practice because the same test can be executed in different ways, which eliminates easy ways of bug reproduction, reduces the trust in the tests, and increases the test maintenance effort.

Tests should be written in a simple and plain way, so everyone who looks at them will understand what step led to the issue.

Setting Up the Sample Project

The Espresso for Android testing framework supports devices running Android 2.3.3 (API level 10) and higher. It was developed for writing UI tests within a single target application. In this book, all the examples were developed and tested with the following environment:
  • Device—Nexus 5X, Android 8.1.0 (API level 27)

  • IDE—AndroidStudio 3.2.1

Let’s start setting up our sample project. It is a simple TO-DO application forked from the googlesamples/android-architecture GitHub repository ( https://github.com/googlesamples/android-architecture ) and modified in a way to show you most of the Espresso use cases.

Here is the link to the GitHub page where you can download the source code or check out the project directly in your AndroidStudio IDE— https://github.com/Apress/android-espresso-revealed . The sample application allows us to add, edit, and delete TO-DO tasks. It contains different types of UI elements without functional load but the variety of the components used there allows us to see Espresso in action.

After the repository is pulled into the AndroidStudio IDE, you will see a todoapp project with one app module. This sample project already contains a test package. Espresso dependencies are added to the build.gradle file . See Figure 1-1. In general, for every test project where Espresso is used, the following steps should be done (this example is based on the TO-DO application):
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig1_HTML.jpg
Figure 1-1

Sample project structure

  1. 1.

    Add an androidTest package inside the application module.

     
  2. 2.

    Set up the Espresso dependencies in the todoapp/app/build.gradle file inside the application module. Put them in the dependencies{...} section.

     
// Android Testing Support Library's runner and rules
androidTestImplementation "com.android.support.test:runner:$rootProject.ext.runnerVersion"
androidTestImplementation "com.android.support.test:rules:$rootProject.ext.rulesVersion"
androidTestImplementation "android.arch.persistence.room:testing:$rootProject.roomVersion"
// Espresso UI Testing
androidTestImplementation "com.android.support.test.espresso:espresso-core:$rootProject.espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-contrib:$rootProject.espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-intents:$rootProject.espressoVersion"
androidTestImplementation "com.android.support.test.espresso.idling:idling-concurrent:$rootProject.espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-idling-resource:$rootProject.espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-web:$rootProject.espressoVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-accessibility:$rootProject.espressoVersion"
  1. 3.
    Put the Espresso dependency versions inside the root project todoapp/build.gradle file (see Figure 1-2). This is not mandatory but is good practice in case there’s a multi-module application structure. Later, instead of updating dependency versions in multiple gradle files, we would only need to update them in one place.
    ../images/469090_1_En_1_Chapter/469090_1_En_1_Fig2_HTML.jpg
    Figure 1-2

    todoapp/build.gradle: keeping dependency versions in one place

    In most cases, after dependencies have been added, changed, or deleted, we must synchronize the project in AndroidStudio by clicking on the Gradle Sync icon ../images/469090_1_En_1_Chapter/469090_1_En_1_Figa_HTML.jpg. You need an Internet connection to download any changed dependencies.

     
  1. 4.

    Add a test package inside the todoapp/app/src/androidTest/java directory. Usually the test package will have the same name as the application being tested, but with a .test postfix.

     

Starting from this moment, you can add your first test class and begin writing tests.

Understanding Android Instrumentation

On Android UI tests, we use the instrumentation mechanism to execute tests. Unlike unit tests, which can run on the JVM directly, instrumented tests run on a real device or emulator. Such tests have access to the Instrumentation API, which enables us to control the test application from our test code, provides access to the context of the application, and allows us to replicate user behavior through different UI actions, like click, swipe, etc. This is achieved because the instrumented test application runs in the same process as the application being tested. Instrumentation will be instantiated before any of the application code, allowing it to monitor the interactions that the system has with the application.

Instrumentation is usually declared in test application Android manifest file using the instrumentation XML tag. Here is the example of instrumentation declaration with the AndroidJUnitRunner from the Android Support library:
<instrumentation
    android:name="android.support.test.runner.AndroidJUnitRunner"
    android:targetPackage="com.example.android.architecture.blueprints.todoapp" />
Here is the same sample for the AndroidX Test library:
<instrumentation
    android:name="androidx.test.runner.AndroidJUnitRunner"
    android:targetPackage="com.example.android.architecture.blueprints.todoapp" />
This also can be achieved by declaring it in the application module build.gradle file:
android {
...
    defaultConfig {
        ...
        applicationId "com.example.android.architecture.blueprints.todoapp"
        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
    }
...
}
android {
...
    defaultConfig {
        ...
        applicationId "com.example.android.architecture.blueprints.todoapp"
        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
    }
...
}

In both cases, we provide the instrumentation test runner name and the target application package, which is the test application package. In the build.gradle file, it is called applicationId. AndroidJUnitRunner is the default Android JUnit test runner, starting from API level 8 (Android 2.2). It allows us to run JUnit3 or JUnit4 based tests.

The test runner handles loading your test package and the test app to a device, running your tests, and reporting the test results.

To access the information about the current test, we run the InstrumentationRegistry class . It holds a reference to the instrumentation object running in the process as well as to the target application context object, the test application context object, and the command-line arguments passed into your test.

A couple of words about annotations used with Espresso:
  • Whenever we create a test class, it or its superclass should be annotated with a @RunWith(AndroidJUnit4.class) annotation. Otherwise, the default JUnit runner will take over the running process and the tests will fail.

  • To execute code once before or after any test method inside the class, the @BeforeClass or @AfterClass JUnit annotations can be used.

  • To execute code before or after each test method inside the class, the @Before or @After JUnit annotations can be used. This can be useful when several tests need similar objects created or deleted before/after they can run.

  • The @Rule annotates fields that reference rules or methods that return a rule. Rules can be used for different purposes. For example, later in the book, we will talk about activity or TestWatcher rules.

Refer to the BaseTest.java class to see how some of the described annotations are used:
@RunWith(AndroidJUnit4.class)
public class BaseTest {
    @Before
    public void setUp() throws Exception {
        setFailureHandler(new CustomFailureHandler(
                InstrumentationRegistry.getInstrumentation().getTargetContext()));
    }
    @Rule
    public ActivityTestRule<TasksActivity> menuActivityTestRule =
        new ActivityTestRule<>(TasksActivity.class);
}

Espresso Basics

Every mobile application has some form of user interface (UI). In the Android world, this is accomplished through the use of View and ViewGroup objects. They are used for drawing UI elements on the Android device screen. From a testing point of view, we are interested in these UI elements to further perform actions or verifications on them. The first step we have to do is locate these views in the application UI.

Identifying Application UI Elements

Before jumping into the Espresso topic, let’s think about the mobile application from the end user perspective. What do users do when they use the application? They:
  1. 1.

    Search for UI elements on the screen (buttons, lists, edit text fields, icons, etc.).

     
  2. 2.

    Perform actions on UI elements (click, double-click, swipe, type, etc.).

     
  3. 3.

    Check the result (text is typed, click led to expected result, list is scrolled, etc.).

     

So, our first task when we start writing automated tests is to find the UI elements in the application we would like to perform actions on. They can be easily located with the help of a couple of tools. The first possibility is to use Android Device Monitor.

To start the standalone Device Monitor application, enter the following on the command line inside the android-sdk/tools/ directory:
monitor
After the Android Device Monitor starts, connect the device. Select it by tapping on the device name inside the Devices tab, open the screen you want to inspect, and click the Phone ../images/469090_1_En_1_Chapter/469090_1_En_1_Figb_HTML.jpg icon (see Figure 1-3). After following these steps, you will be able to inspect the application UI by just clicking on the available elements inside the Android Device Monitor. The details of the element are shown on the right side, including resource ID, text, content description, etc. This information is very important because it will become the base for views identification inside the application UI.
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig3_HTML.jpg
Figure 1-3

Identifying UI elements by clicking on them

The second option is to use Layout Inspector, which is available from the AndroidStudio Tools ➤ Layout Inspector menu. Select the needed activity or fragment and start to investigate the application layout (see Figures 1-4 and 1-5).
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig4_HTML.jpg
Figure 1-4

Selecting a running process from the Layout Inspector

../images/469090_1_En_1_Chapter/469090_1_En_1_Fig5_HTML.jpg
Figure 1-5

Analyzing the application layout from the Layout Inspector

As you can see in Figure 1-5, the Layout Inspector view is more detailed. It provides us with more data compared to the Android Device Monitor, which provides more possibilities for view identification and verification. Another benefit of the Layout Inspector is that it saves the layout dumps inside the /captures folder, which can be easily accessed without the need to start another tool. They can be committed into the source control system and used by multiple team members.

Exercise 1

Inspecting the Application Layout

Now it is time for the first exercise—to build and install the sample TO-DO application on an emulator or real device, launch it, and then make layout dumps with the Monitor and Layout Inspector tools in different application sections. You can then analyze the layouts and understand how these tools work and finally decide which one is better for you.
  1. 1.

    Make the layout dump in the All TO-DOs list and study the hierarchy structure.

     
  2. 2.

    Open a contextual menu toolbar in the All TO-DOs list and create a layout dump. Study the hierarchy structure.

     

Espresso

At this moment, the UI elements are identified with the help of the tools or based on the source code and we can use Espresso to start operating on them. The main Espresso class is the entry point to the Espresso framework and is where core Espresso methods live. Testing can be initiated by using one of the on methods (e.g., onView()) or by performing top-level user actions (e.g., pressBack()).
  • onView() —A ViewInteraction for a given view. Takes the hamcrest ViewMatchers instance(s) as a parameter. You can pass one or more of these to the onView() method to locate a view, based on view properties, within the current view hierarchy.

Note

The view has to be part of the view hierarchy. This may not be the case if it is rendered as part of an AdapterView (e.g., a ListView). If this is the case, use Espresso.onData to load the view first.

  • onData() —A DataInteraction for a data object (e.g., a ListView). Takes as a parameter a hamcrest matcher that matches the data object represented by the single item in the list.

  • pressBack() —A press on the back button. Throws PerformException if the currently displayed activity is a root activity, since pressing the back button would result in the application closing.

  • closeSoftKeyboard() —Closes the soft keyboard if it’s open.

  • openContextualActionModeOverflowMenu() —Opens the overflow menu displayed in the contextual options of an ActionMode.

  • openActionBarOverflowOrOptionsMenu() —Opens the overflow menu displayed within an ActionBar.

We will start with the basic Espresso functionality. First we will see how operations on single views work with the onView() method. As a parameter, it takes a hamcrest matcher to match a view in the application UI. We will learn more about view matchers in the next section.

Espresso ViewMatchers

View matchers form a collection of hamcrest Java matchers that match views. The Espresso ViewMatchers are as follows (I have noted the most frequently used ones based on my experience):
  • isAssignableFrom() —Matches a view based on an instance or subclass of the provided class. Normally used in combination with other ViewMatchers. Commonly used.

  • withClassName() ⎯Returns a matcher that matches views with class name matching the given matcher.

  • isDisplayed() ⎯Returns a matcher that matches views that are currently displayed on the screen to the user. Commonly used.

Note

isDisplayed() will select views that are partially displayed (e.g., the full height/width of the view is greater than the height/width of the visible rectangle). If you want to ensure the entire rectangle is displayed, use isCompletelyDisplayed().

  • isCompletelyDisplayed() ⎯Returns a matcher that only accepts a view whose height and width fit perfectly within the currently displayed region of this view.

Note

There exist views (such as ScrollViews) whose height and width are larger than the physical device screen by design. Such views will never be completely displayed.

  • isDisplayingAtLeast() ⎯Returns a matcher that accepts a view so long as a given percentage of that view’s area is not obscured by any parent view and is thus visible to the user.

  • isEnabled() ⎯Returns a matcher that matches view(s) that are enabled. Commonly used.

  • isFocusable() ⎯Returns a matcher that matches view(s) that are focusable.

  • hasFocus() ⎯Returns a matcher that matches view(s) that currently have focus.

  • isSelected() ⎯Returns a matcher that matches view(s) that are selected.

  • hasSibling() ⎯Returns a matcher that matches view(s) based on their siblings. This may be particularly useful when a view cannot be uniquely selected on properties such as text or view ID. For example, a call button is repeated several times in a contact layout and the only way to differentiate the call button view is by what appears next to it (e.g., the unique name of the contact).

  • withContentDescription() ⎯Returns a matcher that matches view(s) based on the content description property value. Commonly used.

  • withId() ⎯Returns a matcher that matches view(s) based on content description’s id. Commonly used.

Note

Android resource IDs are not guaranteed to be unique. You may have to pair this matcher with another one to guarantee a unique view selection.

  • withResourceName() ⎯Returns a matcher that matches view(s) based on resource ID names, (for instance, channel_avatar).

  • withTagKey() ⎯Returns a matcher that matches view(s) based on tag keys.

  • withTagValue() ⎯Returns a matcher that matches view(s) based on tag property values.

  • withText() ⎯Returns a matcher that matches view(s) based on its text property value.

  • withHint() ⎯Returns a matcher that matches view(s) based on its hint property value.

  • isChecked() ⎯Returns a matcher that accepts it only if the view is a CompoundButton (or a subtype of) and is in checked state. Commonly used.

  • isNotChecked() ⎯Returns a matcher that accepts it only if the view is a CompoundButton (or subtype of) and is not in the checked state. Commonly used.

  • hasContentDescription() ⎯Returns a matcher that matches view(s) with any content description.

  • hasDescendant() ⎯Returns a matcher that matches view(s) based on the presence of a descendant in its view hierarchy.

  • isClickable() ⎯Returns a matcher that matches view(s) that are clickable.

  • isDescendantOfA() ⎯Returns a matcher that matches view(s) based on the given ancestor type.

  • withEffectiveVisibility() ⎯Returns a matcher that matches view(s) that have “effective” visibility set to the given value.

  • withAlpha() ⎯Matches view(s) with the specified alpha value. Alpha is a view property value from 0 to 1, where 0 means the view is completely transparent and 1 means the view is completely opaque.

  • withParent() ⎯A matcher that accepts a view only if the view’s parent is accepted by the provided matcher.

  • withChild() ⎯Matches view(s) whose child is accepted by the provided matcher.

  • hasChildCount() ⎯Matches a ViewGroup (e.g., a ListView) if it has exactly the specified number of children.

  • hasMinimumChildCount() ⎯Matches a ViewGroup (e.g., a ListView) if it has at least the specified number of children.

  • isRoot() ⎯Returns a matcher that matches the root view.

  • hasImeAction() ⎯Returns a matcher that matches views that support input methods.

  • hasLinks() ⎯Returns a matcher that matches TextView(s) that have links.

  • withSpinnerText() ⎯Returns a matcher that matches a descendant of a spinner that is displaying the string of the selected item associated with the given resource ID.

  • isJavascriptEnabled() ⎯Returns a matcher that matches web view(s) if they are evaluating JavaScript.

  • hasErrorText() ⎯Returns a matcher that matches EditText based on the edit text error string value.

  • withInputType() ⎯Returns a matcher that matches android.text.InputType.

  • withParentIndex() ⎯Returns a matcher that matches the child index inside the ViewParent.

As an example, here is the withText() ViewMatcher that is passed to the onView() method to match the view, shown in Figure 1-6, based on its text:
onView(withText("item 1"));    // locating view with todo "item 1"
A similar approach is used to locate the filter view in Figure 1-3 based on the view ID and using the withId() ViewMatcher . You probably know that all Android application assets, from views to strings, are stored in dynamically created R.java files. Therefore, if the target view has an ID value defined by a developer, we are able to locate it by referencing the ID value from the R.java class⎯R.id.view_id:
onView(withId(R.id.menu_filter));    //locating the filter menu item
It is time to look at the official Espresso cheat sheet, available from the following link⎯ https://developer.android.com/training/testing/espresso/cheat-sheet . (See Figure 1-6) At this moment we are interested in ViewMatchers section. You can see that ViewMatchers are grouped into the following focus areas:
  • User properties

  • UI properties

  • Object matchers

  • Hierarchy

  • Input

  • Class

  • Root matchers

../images/469090_1_En_1_Chapter/469090_1_En_1_Fig6_HTML.jpg
Figure 1-6

Espresso cheat sheet 2.1⎯ViewMatchers (source https://developer.android.com/training/testing/espresso/cheat-sheet )

Let’s look at some examples of how these ViewMatchers can be used with our sample application. Open the ViewMatchersExampleTest.java class and look at the test methods. All of them are listed in Figure 1-7.

@Test
public void userProperties() {
    onView(withId(R.id.fab_add_task));
    onView(withText("All TO-DOs"));
    onView(withContentDescription(R.string.menu_filter));
    onView(hasContentDescription());
    onView(withHint(R.string.name_hint));
}
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig7_HTML.jpg
Figure 1-7

List of TO-DOs in the TO-DO application

In the test case, you can see that we identify views on the screen shown in Figure 1-7. The floating action button is identified by its ID⎯onView(withId(R.id.fab_add_task)). The TO-DO items list title is identified based on its text⎯onView(withText("All TO-DOs")). The filter icon in the toolbar is located by the content description text⎯onView(withContentDescription(R.string.menu_filter)). The presence of the content description is in a view or based on a view hint.
@Test
public void uiProperties() {
    onView(isDisplayed());
    onView(isEnabled());
    onView(isChecked());
}
In this test case, there are examples of identifying views by their UI appearance. Based on the screen in Figure 1-7, we see that most of the views are displayed and enabled. That means onView(isDisplayed()) or onView(isEnabled()) can’t be used without additional matchers, because the tests will fail with ambiguous matching exceptions. In the following test case, you can see how two matchers are combined into a sequence of matchers with the help of the allOf() hamcrest logical matcher. It will return the matched object only when all the matchers inside it successfully execute. See Figures 1-8 through 1-10. In a later section, you will learn more about the hamcrest matchers.
@Test
public void objectMatcher() {
    onView(not(isChecked()));
    onView(allOf(withText("item 1"), isChecked()));
}
@Test
public void hierarchy() {
    onView(withParent(withId(R.id.todo_item)));
    onView(withChild(withText("item 2")));
    onView(isDescendantOfA(withId(R.id.todo_item)));
    onView(hasDescendant(isChecked()));
    onView(hasSibling(withContentDescription(R.string.menu_filter)));
}
@Test
public void input() {
    onView(supportsInputMethods());
    onView(hasImeAction(EditorInfo.IME_ACTION_SEND));
}
@Test
public void classMatchers() {
    onView(isAssignableFrom(CheckBox.class));
    onView(withClassName(is(FloatingActionButton.class.getCanonicalName())));
}
@Test
public void rootMatchers() {
    onView(isFocusable());
    onView(withText(R.string.name_hint)).inRoot(isTouchable());
    onView(withText(R.string.name_hint)).inRoot(isDialog());
    onView(withText(R.string.name_hint)).inRoot(isPlatformPopup());
}
@Test
public void preferenceMatchers() {
    onData(withSummaryText("3 days"));
    onData(withTitle("Send notification"));
    onData(withKey("example_switch"));
    onView(isEnabled());
}
@Test
public void layoutMatchers() {
    onView(hasEllipsizedText());
    onView(hasMultilineText());
}
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig8_HTML.jpg
Figure 1-8

EditText example from the General preferences in the application settings

../images/469090_1_En_1_Chapter/469090_1_En_1_Fig9_HTML.jpg
Figure 1-9

General preferences section in the application settings

../images/469090_1_En_1_Chapter/469090_1_En_1_Fig10_HTML.jpg
Figure 1-10

The TO-DO task detail view

We will not discuss the cursor matchers shown in the ViewMatchers section of the Espresso spreadsheet, because their goal is to operate at a database level, which is used in unit and integration tests and is therefore out of this book’s scope.

Now let’s take a step aside from our sample application and look at some examples of hamcrest string matchers. For simplicity, the string "XXYYZZ" will be used as an expected text pattern. The Espresso ViewMatcher class implements two string-matcher methods— withText() and withContentDescription() . They match a view with text that’s equal to the expected text or the expected content description:
onView(withText("XXYYZZ")).perform(click());
onView(withContentDescription("XXYYZZ")).perform(click());
Using Hamcrest string matchers, we can create more flexible matcher combinations. We can match a view with text that starts with the "XXYY" pattern:
onView(withText(startsWith("XXYY"))).perform(click());
We can match a view with text that ends with a "YYZZ" pattern:
onView(withText(endsWith("YYZZ"))).perform(click());
We can assert that the text of a particular view with specified R.id has a content description that contains the "YYZZ" string anywhere:
onView(withId(R.id.viewId)).check(matches(withContentDescription(containsString("YYZZ"))));
We can match a view with text that’s equal to the specified string, ignoring case:
onView(withText(equalToIgnoringCase("xxYY"))).perform(click());
We can match a view with text that’s equal to the specified text when whitespace differences are (mostly) ignored:
onView(withText(equalToIgnoringWhiteSpace("XX YY ZZ"))).perform(click());
We can assert that the text of a particular view with specified R.id does not contain the "YYZZ" string:
onView(withId(R.id.viewId)).check(matches(withText(not(containsString("YYZZ")))));
Adding the allOf() or anyOf() hamcrest core matchers gives us even more power. We can assert that the text of a particular view with a specified R.id doesn’t start with the "ZZ" string and contains the "YYZZ" string anywhere:
onView(withId(R.id.viewId))
    .check(matches(allOf(withText(not(startsWith("ZZ"))),
        withText(containsString("YYZZ")))));
We can also assert that the text of a particular view with a specified R.id ends with the "ZZ" string or contains the "YYZZ" string anywhere:
onView(withId(R.id.viewId))
    .check(matches(anyOf(withText(endsWith("ZZ")),
        withText(containsString("YYZZ")))));

To get a full overview of the hamcrest matchers, refer to their official documentation at http://hamcrest.org/JavaHamcrest .

So, now we have an understanding of ViewMatchers. We also understand that they play one of the most important roles in the Espresso testing framework. Their task is to locate the matched view inside the application layout or fail if the match did not happen.

Espresso’s ViewInteraction Class

In the previous examples, we were doing new perform() and check() operations on the views. These methods are representatives of the ViewInteraction class . Interactions act like glue between the ViewMatcher and the ViewAssertion or ViewAction.

Each interaction is tied to the view that was previously located by the ViewMatcher. You probably guessed based on the method names that the perform() method takes an action and the check() method asserts some condition provided as a parameter. There is one more ViewInteraction we haven’t used yet⎯inRoot().
  • perform()⎯Receives a view action or a set of view actions as a parameter and performs them on the view selected by the current ViewMatcher.

  • check()⎯Receives a view action or a set of view actions as a parameter and checks it on the the view selected by the current ViewMatcher.

So, what about the inRoot() method then? With this view interaction, we are targeting the multi-window states in our application. For example, the AutoComplete window layout that is drawn over the test application. In this case, we should explicitly indicate which window Espresso should operate on by matching the proper window with the RootMatcher.
  • inRoot()⎯Receives a root matcher as a parameter and sets the scope of the view interaction to the root view, identified by the root matcher.

Note

Espresso performs all the actions on the UI thread, which means that it will first wait for the application UI to render and only after that perform the required steps. This ensures that the application UI elements are fully loaded and displayed on the screen, which increases test reliability and robustness. It will also eliminate the need of having waits and sleeps in the tests.

In the following section, you will see examples of how perform() and check() view interactions can be used.

Espresso’s ViewActions Class

As you may guess, a ViewAction is responsible for performing actions on a required view. The target is to replicate the end user behavior by interacting with the UI elements on the screen. Here are examples of the type of actions we can perform (see Figure 1-11):
  • clearText() ⎯Returns an action that clears text on the view. The view must be displayed on the screen.

  • click()⎯Returns an action that clicks the view. At least 90% of the view must be displayed on the screen.

  • swipeLeft() ⎯Returns an action that performs a swipe right-to-left across the vertical center of the view. The swipe doesn’t start at the very edge of the view, but is a bit offset, since swiping from the exact edge may cause unexpected behavior (e.g., it may open a navigation drawer). Other swipe actions defined by Espresso are swipeRight(), swipeDown(), and swipeUp(). For all the swipe actions, at least 90% of the views must be displayed onscreen.

  • closeSoftKeyboard() ⎯Returns an action that closes the soft keyboard. If the keyboard is already closed, it is non-operational.

  • pressImeActionButton() ⎯Returns an action that presses the current action button (Next, Done, Search, etc.) on the IME (Input Method Editor).

  • pressBack() ⎯Returns an action that clicks the hardware back button.

  • pressMenuKey() ⎯Returns an action that presses the hardware menu key. Most modern devices on the market no longer support the hardware menu key, so this method is rarely used.

  • pressKey() ⎯Returns an action that presses the key specified by the key code (e.g., KeyEvent.KEYCODE_BACK). There is a huge list of all possible key codes declared in the andrid.view.KeyEvent.java class.

  • doubleClick() ⎯Similar to the click() action, this returns an action that double-clicks the view. At least 90% of the view must be displayed onscreen.

  • longClick() ⎯Returns an action that long-clicks the view. At least 90% of the view must be displayed onscreen.

  • scrollTo()⎯Returns an action that scrolls to the view. Based on the current implementation, the view we would like to scroll to must be a descendant of one of the following classes: ScrollView.class, HorizontalScrollView.class, ListView.class. At least 90% of the view must be displayed onscreen.

Note

The scrollTo() action will have no effect if the view is already displayed.

  • typeText()⎯Returns an action that selects the view (by clicking on it) and types the provided string into the view. Appending an ' ' to the end of the string translates to a Enter key event. The view must be displayed onscreen and must support input methods.

Note

The typeText() method performs a tap on the view before typing to force the view into focus. If the view already contains text, this tap may place the cursor at an arbitrary position within the text.

  • replaceText() ⎯Returns an action that updates the text attribute of a view.

  • openLink() ⎯Returns an action that opens a link matching the given link text and URI matchers. The action is performed by invoking the link’s onClick method (as opposed to actually issuing a click on the screen).

../images/469090_1_En_1_Chapter/469090_1_En_1_Fig11_HTML.jpg
Figure 1-11

Espresso cheat sheet 2.1—ViewActions (source https://developer.android.com/training/testing/espresso/cheat-sheet )

The Espresso cheat sheet in Figure 1-11 shows that all the actions are split into three categories:
  • Click/Press actions

  • Gestures

  • Text-related actions

From my point of view, we can add one more type here, which will probably come in the next cheat sheet version:
  • Conditional actions

These types of actions are represented by one method at this time— repeatedlyUntil() . It enables performing a given action on a view until it reaches the desired state matched by the given ViewMatcher. This action is useful when you’re performing the action repeatedly on a view and then it changes its state at runtime. A good use case to automate with this view action is going through the walkthrough or on-boarding screens from the beginning until the end.

As you can see, Espresso provides almost all the actions needed to cover the end user behavior, but still lacks some. The examples may be:
  • Drag and drop actions

  • Multi-gesture actions like pinch to zoom

Having in our hands the Espresso core methods—ViewInteractions, ViewMatchers, and ViewActions—we can start to automate simple use cases of our example TO-DO application. Let’s come up with some:
  • Add a new TO-DO that provides the title and description. Verify it is shown in the TO-DO list.

  • Add a new TO-DO, mark it completed, and verify it is in the list of completed TO-DOs.

  • Add a new TO-DO, edit it, and verify the changes.

Refer to the ViewActionsTest to see the example code. The first, second, and third use cases are shown in the addsNewToDo() , checksToDoStateChange() , and editsToDo() test cases, respectively. We will drill down into one of them to see some details:
@Test
public void checksToDoStateChange() {
    // adding new TO-DO
    onView(withId(R.id.fab_add_task)).perform(click());
    onView(withId(R.id.add_task_title))
            .perform(typeText(toDoTitle), closeSoftKeyboard());
    onView(withId(R.id.add_task_description))
            .perform(typeText(toDoDescription), closeSoftKeyboard());
    onView(withId(R.id.fab_edit_task_done)).perform(click());
    // marking our TO-DO as completed
    onView(withId(R.id.todo_complete)).perform(click());
    // filtering out the completed TO-DO
    onView(withId(R.id.menu_filter)).perform(click());
    onView(allOf(withId(android.R.id.title), withText(R.string.nav_completed)))
        .perform(click());
    onView(withId(R.id.todo_title))
        .check(matches(allOf(withText(toDoTitle), isDisplayed())));
}

Note that we introduced the TestData class to keep all the methods that generate input data. This helps reduce the test method boilerplate code. You may notice that we add a unique timestamp in milliseconds to each TO-DO item title and description. This keeps our test data unique, which simplifies a lot of view identification and validation inside the application layout.

Now, regarding the Espresso test code. Note the single combination of ViewInteraction, ViewMatcher, and ViewAction, visible in the following line of code:
onView(withId(R.id.fab_add_task)).perform(click());
There are also examples of taking multiple view actions as parameters by the perform() view interaction:
onView(withId(R.id.add_task_title))
        .perform(typeText(toDoTitle), closeSoftKeyboard());
There are also examples of how multiple ViewMatchers can be combined to give us a stronger combination of conditions to match the desired view or validate its state, or to avoid extra lines of code. The maximum number of matchers that can be provided to the allOf() matcher is six:
onView(allOf(withId(android.R.id.title), withText(R.string.nav_completed)))
        .perform(click());
onView(withId(R.id.todo_title))
        .check(matches(allOf(withText(toDoTitle), isDisplayed())));

Notice how the Espresso notation is flexible—allOf() matcher can be used both inside the onView() method and inside the check(matches()) view interaction.

Exercise 2

Writing Your First Espresso Test Cases

Based on the examples in ViewActionsTest, write test cases for the following application functionality:
  1. 1.

    Add a TO-DO and mark it as completed. Verify that the checkbox of the completed TO-DO is checked.

     
  2. 2.

    Add a new TO-DO, open the TO-DO details by clicking on it (hint: use withText() matcher), and delete it by clicking the Delete Task button. Verify the the All TO-DOs list is empty (i.e., verify that the text “You have no TO-DOs!” and that the ID R.id.noTasksIcon are displayed onscreen).

     

Espresso’s DataInteraction Class

As mentioned in the “Understanding Android Instrumentation” section, the Android application represents its elements via the View or ViewGroup. Single UI elements are drawn inside the View. The ViewGroup is used to represent a set of views or another view group. Think about ViewGroup as a container of UI elements. To represent a list of objects in Android, you can use a class called AdapterView, which extends the ViewGroup class and whose child views are determined by the Adapter. Another possibility to represent a list of objects is to use the RecyclerView, but we discuss it in later chapters.

Thus, Adapter is responsible for transforming the data from an external source into the View that’s bound to AdapterView . In the end, AdapterView contains many of views with the data produced by Adapter and forms a list of items, which is called the ListView (see Figure 1-12).
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig12_HTML.jpg
Figure 1-12

ListView visualization (source https://developer.android.com/guide/topics/ui/layout/listview )

To operate on the such lists, Espresso provides the DataInteraction interface , which allows us to interact with elements displayed inside AdapterViews. Let’s briefly go through the commonly used DataInteraction methods :
  • atPosition(Integer atPosition)⎯Selects the view that matches the nth position on the adapter based on the data matcher.

  • inAdapterView(Matcher<View> adapterMatcher)⎯Points to a specific adapter view on the screen to operate on. Should be used when we have two or more AdapterViews in one layout. An example may be the layout with a list view and a menu drawer list view.

  • inRoot(Matcher<Root> rootMatcher)⎯Causes the data interaction to work within the root window specified by the given root matcher. May be useful when we have an AutoComplete list view popping up over the application window.

  • onChildView(Matcher<View> childMatcher)⎯Redirects perform and check actions to the view inside the adapter item returned by Adapter.getView().

Now, let’s see how DataInteraction methods are used in a test case written for one of the setting functionalities. The Settings application was implemented using the Android Preference component—the UI building block displayed by a PreferenceActivity in the form of a ListView. This class provides the view to be displayed in the activity and associates with a SharedPreferences to store/retrieve the preference data. When specifying a preference hierarchy in XML, each element can point to a subclass of Preference, similar to the view hierarchy and layouts. This class contains a key that will be used as the key into the SharedPreferences.

As you can see in Figure 1-13, the main Settings section contains a list with four preference headers (General, Notifications, Data&Sync and WebView sample), where each header contains subsections with lists of preferences.
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig13_HTML.jpg
Figure 1-13

The dataInteraction() test case flow, starting from the Settings section

Open the DataInteractionsTest class to see the code examples.

@Test
public void dataInteraction() {
    openDrawer();
    onView(allOf(withId(R.id.design_menu_item_text),
            withText(R.string.settings_title))).perform(click());
    // start of the flow as shown in Figure 1-13
    onData(instanceOf(PreferenceActivity.Header.class))
            .inAdapterView(withId(android.R.id.list))
            .atPosition(0)
            .onChildView(withId(android.R.id.title))
            .check(matches(withText("General")))
            .perform(click());
    onData(withKey("email_edit_text"))
            /*we have to point explicitly to the parent of the General prefs list
            because there are two {@ListView}s with id android.R.id.list in the hierarchy*/
            .inAdapterView(allOf(withId(android.R.id.list), withParent(withId(android.R.id.list_container))))
            .check(matches(isDisplayed()))
            .perform(click());
    onView(withId(android.R.id.edit)).perform(replaceText("[email protected]"));
    onView(withId(android.R.id.button1)).perform(click());
    onData(withKey("email_edit_text"))
            .inAdapterView(allOf(withId(android.R.id.list), withParent(withId(android.R.id.list_container))))
            .onChildView(withId(android.R.id.summary))
            .check(matches(withText("[email protected]")));
}
To understand better how DataInteraction methods work, we will split our test case into two parts. The first part operates on the main Settings sections with the four headers:
    onData(instanceOf(PreferenceActivity.Header.class))
            .inAdapterView(withId(android.R.id.list))
            .atPosition(0)
            .onChildView(withId(android.R.id.title))
            .check(matches(withText("General")))
            .perform(click());
First, we explicitly point out that the object we should operate on is its instance of PreferenceActivity.Header.class:
instanceOf(PreferenceActivity.Header.class)
Second, we point out which adapter contains our object. Inside an adapter of the Android default ListView component, with an ID of android.R.id.list, at position “0”. This is the first row in our list:
inAdapterView(withId(android.R.id.list)).atPosition(0)
Third, we point out that we would like to operate on the child view of our list item with ID android.R.id.title that matches the text "General" and perform a click on it:
onChildView(withId(android.R.id.title)).check(matches(withText("General"))).perform(click())
Moving on to the second part of our test case, which operates on the subsection of the General Settings section:
    onData(withKey("email_edit_text"))
            .inAdapterView(allOf(withId(android.R.id.list), withParent(withId(android.R.id.list_container))))
            .check(matches(isDisplayed()))
            .perform(click());
    onView(withId(android.R.id.edit)).perform(replaceText("[email protected]"));
    onView(withId(android.R.id.button1)).perform(click());
    onData(withKey("email_edit_text"))
            .inAdapterView(allOf(withId(android.R.id.list), withParent(withId(android.R.id.list_container))))
            .onChildView(withId(android.R.id.summary))
            .check(matches(withText("[email protected]")));
Here, you may observe the preference matcher withKey("email_edit_text") pointing to the EditTextPreference component by its key, which is set in the pref_general.xml file:
withKey("email_edit_text")
We again point to the ID of the adapter view our entry belongs to, combining it with additional matcher to avoid multiple view matching. We check that such an object is displayed on the screen and click on it:
.inAdapterView(allOf(
      withId(android.R.id.list),
      withParent(withId(android.R.id.list_container))))
.check(matches(isDisplayed()))
.perform(click())
At the very end, after the email is typed into the edit text field, we validate that the summary of the list item matches the email we provided:
.inAdapterView(allOf(
      withId(android.R.id.list),
      withParent(withId(android.R.id.list_container))))
.onChildView(withId(android.R.id.summary))
.check(matches(withText("[email protected]")))
Let’s summarize what we have learned about DataInteractions with the help of the Espresso cheat sheet shown in Figure 1-14. The Espresso onData() method is used to operate on the object inside the list view. The list view item is identified by one or by a combination of data options. After an object is identified and located, we can perform actions or do assertions on it.
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig14_HTML.jpg
Figure 1-14

Espresso cheat sheet—DataInteraction (source https://developer.android.com/training/testing/espresso/cheat-sheet )

Exercise 3

Writing a Test Case that Operates on a ListView

Based on examples in the DataInteractionsTest :
  1. 1.

    Write a test case that navigates to the Notifications Settings section and clicks the Enable Notifications toggle by text or by ID. Use the Layout Inspector tool to analyze the Notifications Section layout

     
  2. 2.

    Expand the case from Step 1 and verify that after Enable Notification toggle is switched on, the other notification settings are displayed on the screen.

     

Operating on RecyclerView Using Espresso

RecyclerView is one of the most commonly used views in Android development. It is a more advanced version of the ListView. Whether your application is an image gallery, a news app, or a messenger, a RecyclerView is usually the best tool to implement it. That is why understanding how to properly write automated tests for this component is so important.

Similar to the simple view, Espresso has a RecyclerViewActions class that contains all the actions you can perform on a RecyclerView, but unfortunately Espresso doesn’t provide RecyclerView matchers. For now, we will look at the RecyclerViewActions examples and in Chapter 2 you will see how to create your own RecyclerView matchers.

We will again refer to our sample TO-DO application, where a list of TO-DOs is represented by a RecyclerView component.

RecyclerViewActions

The current class represents view actions that can interact on a RecyclerView. At first look, you may think that we can apply the onData() method here because a RecyclerView is used to display the list of items, but in fact a RecyclerView is not an AdapterView, hence it cannot be used with it. So, to operate on a RecyclerView, we use onView() with a RecyclerView matcher to match the item or its child inside the RecyclerView list. Then we have to perform a RecyclerViewAction or a simple ViewAction on it.
  • actionOnItem(final Matcher<View> itemViewMatcher, final ViewAction viewAction)⎯Returns a ViewAction that scrolls a RecyclerView to the view matched by viewHolderMatcher.

  • actionOnHolderItem(final Matcher<VH> viewHolderMatcher, final ViewAction viewAction)⎯Performs a ViewAction on a view matched by viewHolderMatcher. First it scrolls a RecyclerView to the view matched by itemViewMatcher and then performs an action on the matched view.

  • actionOnItemAtPosition(final int position, final ViewAction viewAction)⎯First it scrolls a RecyclerView to the view matched by itemViewMatcher and then performs an action on the view at position.

  • scrollToHolder(final Matcher<VH> viewHolderMatcher)⎯Returns a ViewAction that scrolls a RecyclerView to the view matched by viewHolderMatcher.

  • scrollTo(final Matcher<View> itemViewMatcher)⎯ViewAction that scrolls a RecyclerView to the view matched by itemViewMatcher.

  • scrollToPosition(final int position)⎯ViewAction that scrolls a RecyclerView to a given position. The view we operate on must be assignable from a RecyclerView class and should be displayed on the screen.

The following code shows how RecyclerViewActions are used in real tests (the same test case is present in the RecyclerViewActionsTest.java class):
@Test
public void addNewToDos() throws Exception {
    generateToDos(12);
    onView(withId(R.id.tasks_list))
            .perform(actionOnItemAtPosition(10, scrollTo()));
    onView(withId(R.id.tasks_list))
            .perform(scrollToPosition(1));
    onView(withId(R.id.tasks_list))
            .perform(scrollToPosition(12));
    onView(withId(R.id.tasks_list))
            .perform(actionOnItemAtPosition(12, click()));
    Espresso.pressBack();
    onView(withId(R.id.tasks_list))
            .perform(scrollToPosition(2));
}

You can omit for now the generateToDos() method and methods that take view holder matchers as parameters (like scrollToHolder() and actionOnHolderItem() ). They will be discussed in Chapter 2. These tests add 12 TO-DOs, so that some of them are not visible on the device screen. The important information here is that RecyclerView adapter knows about all the 12 TO-DO items, but ViewActions can be performed only on items that are displayed to the user. Here, the scrollToPosition() view holder ViewAction helps us do the scrolling and make the needed TO-DO item visible on the screen. Then, the view action can be performed without issues.

You may notice that both cases can perform the same actions and they are both valid:
onView(withId(R.id.tasks_list))
            .perform(actionOnItemAtPosition(12, scrollTo()));
and
onView(withId(R.id.tasks_list))
            .perform(scrollToPosition(12));
As a side note, the current test case is a good example of how can we chain perform() actions if the same view is used in the onView() method R.id.tasks_list. This test case may look like this:
@Test
public void addNewToDosChained() throws Exception {
    generateToDos(12);
    onView(withId(R.id.tasks_list))
            .perform(actionOnItemAtPosition(10, scrollTo()))
            .perform(scrollToPosition(1))
            .perform(scrollToPosition(12))
            .perform(actionOnItemAtPosition(12, click()))
            .perform(pressBack())
            .perform(scrollToPosition(2));
}

The chained test case required only one change—the ViewActions.pressBack() method was used instead of Espresso.pressBack() .

Exercise 4

Experimenting with RecyclerView Actions
  1. 1.

    Based on the examples here, experiment with actions in a RecyclerView. Try to perform actions on the non-visible TO-DO items without scrolling to them and observe the results.

     

Running Espresso Tests from AndroidStudio

At this moment, we have a basic understanding on how to write automated tests with Espresso. Let’s see how our Espresso tests can be run. This is achievable in two ways⎯via AndroidStudio or from the command line.

Before jumping into running tests, we should understand the concept of the Gradle BuildVariant in AndroidStudio. The BuildVariant represents the process that converts the project into an Android Application Package (APK). The Android build process is very flexible and enables you to create a custom build configuration without modifying the application source code. The flexibility is achieved by BuildVariants, which are the the combined product of build type and product flavor.

Build types define certain properties that Gradle uses when building and packaging your application and are typically configured for different stages of your development lifecycle. For example, the debug build type enables debug options and signs the APK with the debug key, while the release build type may shrink, obfuscate, and sign your APK with a release key for distribution.

The product flavor represents different versions of your app that you may release to users, such as free and paid versions of your app. You can customize product flavors to use different code and resources, while sharing and reusing the parts that are common to all versions of your app.

Figure 1-15 shows the Android build process.
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig15_HTML.jpg
Figure 1-15

Android build process (source https://developer.android.com/studio/build )

In short, unlike the release APK, the debug APK will contain debug or test dependencies (such as Espresso dependencies) and test resources needed for our UI tests to run. Therefore, it is important first to have the debug build type, as shown in the following build.gradle file :
buildTypes {
    debug {
        minifyEnabled true
        useProguard false
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguardTest-rules.pro'
    }
    release {
        minifyEnabled true
        useProguard true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        testProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguardTest-rules.pro'
    }
}
Second, we must select the proper BuildVariant for our application module in AndroidStudio, as shown in Figure 1-16.
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig16_HTML.jpg
Figure 1-16

Select a BuildVariant in AndroidStudio

When the proper build type is selected, we can right-click on the test class or test method and create the run configuration for the UI test, as shown in Figure 1-17.
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig17_HTML.jpg
Figure 1-17

Creating an instrumentation test configuration

Then we select Create from the popup menu and confirm it with the OK button. See Figure 1-18.
../images/469090_1_En_1_Chapter/469090_1_En_1_Fig18_HTML.jpg
Figure 1-18

Creating an instrumentation test run configuration

When these steps are done, we are ready to run the selected test. We do so by clicking the arrow ../images/469090_1_En_1_Chapter/469090_1_En_1_Figc_HTML.jpg button in the AndroidStudio toolbar.

Running Espresso Tests from the Terminal

There are different ways to run Espresso and Android Instrumentation tests from the terminal. Among them are:
  • Running Instrumentation tests using shell commands

  • Running Instrumentation tests using Gradle commands

Running Instrumentation Tests Using Shell Commands

The following shell command can be used to run tests located in the app module.

Running App Module Tests with the Android Testing Support Library.
adb shell am instrument -w com.example.android.architecture.blueprints.todoapp.mock.test/android.support.test.runner.AndroidJUnitRunner
Running App Module Tests with the AndroidX Test Library.
adb shell am instrument -w com.example.android.architecture.blueprints.todoapp.test/androidx.test.runner.AndroidJUnitRunner

If you want to run tests from specific test classes, you would add the -e class <Class> parameter to the previous command.

Running Tests from the chapter1.actions.ViewActionsTest.java Class with the Android Testing Support Library.
adb shell am instrument -w -r -e debug false -e class com.example.android.architecture.blueprints.todoapp.test.chapter1.actions.ViewActionsTest com.example.android.architecture.blueprints.todoapp.test/android.support.test.runner.AndroidJUnitRunner
Running Tests from the chapter1.actions.ViewActionsTest.java Class with the AndroidX Test Library.
adb shell am instrument -w -r -e debug false -e class com.example.android.architecture.blueprints.todoapp.test.chapter1.actions.ViewActionsTest#addsNewToDo com.example.android.architecture.blueprints.todoapp.mock.test/androidx.test.runner.AndroidJUnitRunner

In order to run specific test methods or functions, the class parameter can be extended with the #<testMethod> value, as shown next.

Running Tests from the chapter1.actions.ViewActionsTest.addsNewToDo() Test with the Android Testing Support Library.
adb shell am instrument -w -r -e debug false -e class com.example.android.architecture.blueprints.todoapp.test.chapter1.actions.ViewActionsTest#addsNewToDo com.example.android.architecture.blueprints.todoapp.test/android.support.test.runner.AndroidJUnitRunner
Running Tests from the chapter1.actions.ViewActionsTest.addsNewToDo() Test with the AndroidX Test Library.
adb shell am instrument -w -r -e debug false -e class com.example.android.architecture.blueprints.todoapp.test.chapter1.actions.ViewActionsTest#addsNewToDo com.example.android.architecture.blueprints.todoapp.mock.test/androidx.test.runner.AndroidJUnitRunner

If you want to run tests configured to use the Android Test Orchestrator, the following shell command should be used.

Running the chapter1.actions.ViewActionsTest.addsNewToDo() Test with the Android Testing Support Library.
adb shell CLASSPATH=$(adb shell pm path android.support.test.services) app_process / android.support.test.services.shellexecutor.ShellMain am instrument -r -w -e targetInstrumentation com.example.android.architecture.blueprints.todoapp.mock.test/android.support.test.runner.AndroidJUnitRunner   -e debug false -e class 'com.example.android.architecture.blueprints.todoapp.test.chapter1.actions.ViewActionsTest#addsNewToDo' -e clearPackageData true android.support.test.orchestrator/android.support.test.orchestrator.AndroidTestOrchestrator
Running the chapter1.actions.ViewActionsTest.addsNewToDo() Test with the AndroidX Test Library.
adb shell CLASSPATH=$(adb shell pm path androidx.test.services) app_process / androidx.test.services.shellexecutor.ShellMain am instrument -r -w -e targetInstrumentation com.example.android.architecture.blueprints.todoapp.mock.test/androidx.test.runner.AndroidJUnitRunner   -e debug false -e class 'com.example.android.architecture.blueprints.todoapp.test.chapter1.actions.ViewActionsTest#addsNewToDo' -e clearPackageData true androidx.test.orchestrator/androidx.test.orchestrator.AndroidTestOrchestrator

Running Instrumentation Tests Using Gradle Commands

The following Gradle command should be used in order to run all the tests from the app project module (the current directory must be the project’s root directory):
./gradlew app:connectedAndroidTest
Note that for our sample application project (and for many other projects you may work with), in order to test the application, it should be built with the debug build type. On top of this, we have different flavors—mock and prod—as stated in the build.gradle file. That means that the command to run all the tests from the app module will change to reflect the build type and flavor, as shown here:
./gradlew app:connectedMockDebugAndroidTest
As is the case with shell commands, Gradle commands also can accept additional arguments in order to run a specific test class or test method. Here is an example of running the tests from a specific test class:
./gradlew app:connectedMockDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.android.architecture.blueprints.todoapp.test.chapter1.actions.ViewActionsTest

Similar to the shell commands, the class parameter in Gradle can be extended with the #<testMethod> value.

Running the chapter1.actions.ViewActionsTest.checksToDoStateChange() Test.
./gradlew app:connectedMockDebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.example.android.architecture.blueprints.todoapp.test.chapter1.actions.ViewActionsTest#checksToDoStateChange

Exercise 5

Creating a Test Run Configuration
  1. 1.

    Create a test run configuration for a test method, a test class, and a package. Run the tests.

     
  2. 2.

    Edit one of the configurations by navigating to the AndroidStudio menu Run ➤ Edit Configurations.... Remove one of the configurations.

     
  3. 3.

    Practice running a test class or a specific test method using the shell terminal commands.

     
  4. 4.

    Practice running a test class or a specific test method using the gradle terminal commands.

     

Summary

In this first chapter, you learned all about the Espresso basics, starting from the dependencies declaration to writing a simple test, which will be the foundation for more advanced examples described later in this book. In addition to that, you received information about how the application layout should be inspected using the Monitor and Layout Inspector tools, how the build process looks, and how Espresso tests are configured and run from the AndroidStudio IDE.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.180.43