In the first exercise you have been working with the methods of the Espresso Core API:

  • In the first line of our test script you located a View by using its ID as an identifier, and a click was performed on that View.
  • In the second line, you located a different View, again by using its ID as an identifier, but this time a check was performed on that View.

In this part of the course, we are going to take a closer look at the Espresso Core API: the different ways to locate a View, actions to perform and how to check Views. At the end, you will write your own test from scratch in exercise 2.

Locate, perform and check

As I stated earlier, the basics of what we are doing when automating is: locating Views, performing actions on those Views, and finally checking Views. This is the Espresso Core API:

  • onView()
  • .perform()
  • .check()

The onView method allows us to locate a View. It returns a ViewInteraction object for that View, that offers us two methods: perform() and check().

In order to make those methods do what we want, we will have to tell them which View we would like to find, which action to perform, or which check to do. We can do that by using specific arguments. The Espresso Core API methods accept the following parameter types:

  • onView(ViewMatcher)
  • .perform(ViewAction)
  • .check(ViewAssertion)

In the first exercise you’ve already seen (and used) examples of this:

  • withId() – an example of this ViewMatcher;
  • click() – an example of a ViewAction;
  • matches() – an example of a ViewAssertion.

In the next sections we will discuss different examples of ViewMatchers, ViewActions and ViewAssertions and I will give you a few examples of how to use them.

ViewMatchers

There are different ViewMatchers available that allow you to find Views by targeting specific properties. The first one that we have used is the ‘withId()’ ViewMatcher, that allows us to find a View with a specific ID. Other examples include:

  • withText(“example text”)
  • hasDescendant(ViewMatcher)
  • isDisplayed()

The ‘withText’ ViewMatcher is pretty straightforward: it allows you to look for a View with a specific text.

The ‘hasDescendant’ ViewMatcher is a bit more interesting. It is an example of a ‘nested ViewMatcher’. It allows you to find a View based on properties of one of the Views it contains (its descendant), and it has a parameter of type ViewMatcher. This means that it expects you to provide it with another ViewMatcher that matches it’s descendant, for example:

onView(hasDescendant(withText("example text of its descendant")))

The ‘isDisplayed’ ViewMatcher allows you to find a View that is currently displayed. That means it should be visible on screen. This can be a bit problematic, because of the following rule:

  • A ViewMatcher should match one View, no more, no less!

If the ViewMatcher you are using matches multiple Views, you will get an AmbiguousViewMatcherException, and your test will fail. As you can imagine, at any given time there will me more than one View displayed. That is why the ‘isDisplayed’ ViewMatcher will be most useful when construction a combination of ViewMatchers. We can use the following methods to do that:

  • allOf(ViewMatcher, ViewMatcher)
  • anyOf(ViewMatcher, ViewMatcher)

An example of how to use this, would be:

onView(allOf(withText("example text"), withId(R.id.example_id)))

This will match a View that has the ID ‘example_id’ and the text “example text’. If a View has only one of these properties, it will not be matched.

Another example would be:

onView(allOf(withText("example text"), hasDescendant(withId(R.id.example_id))))

ViewActions

Once a View has been found, we have to tell Espresso what kind of action we would like to perform on that View. We can do that by supplying a ViewAction to the perform method. Examples of ViewActions are:

  • click()
  • scrollTo()
  • swipeLeft()
  • typeText(String)

You can use a single ViewAction:

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

But it’s also possible to provide multiple at once:

onView(withId(R.id.example_id)).perform(click(), typeText("Hello World"))

ViewAssertions

ViewAssertions allow us to check a View for something. We can do that by calling the ‘check’ method and providing a ViewAssertion. Examples of ViewAssertions are:

  • doesNotExist()
  • matches(ViewMatcher)

The ‘matches’ ViewAssertion allows us to check a View for specific properties that we can provide in the form of a ViewMatcher. These are the same ViewMatchers that we can use to find a View. Examples of their usage:

onView(withId(R.id.example_id)).check(matches(withText("Hello World")))
onView(withId(R.id.example_id)).check(matches(isDisplayed()))
onView(withText("Hello World")).check(withId(R.id.example_id))
onView(withId(R.id.example_id)).check(doesNotExist())

For more ViewMatchers, ViewActions and ViewAssertions, see: Google’s Espresso Cheat Sheet

Exercise 2: Select, Click and Scroll

In this exercise you will write your own test from scratch.

Prerequisites: Make sure you completed exercise 1.

Objective: Write a test where you:

  • add 2 shots of espresso
  • add chocolate
  • select ‘Review Order’
  • verify on the Order Overview screen that the text contains the right ingredients.

The Views that you need are marked in the images below:

Test steps:

  • Create a method that you annotate with ‘@Test
  • Find the right Views with the Layout Inspector
  • Use the Espresso Core API to find the Views and perform the necessary actions in the Custom Order screen
  • Use the Espresso Core API to find the View in the Order Overview screen and check if the text matches with your expectation
  • Press the green ‘play’ button next to your test method to run the test and make sure it passes.

The solution can be found at GitHub:
Solution Exercise 2

A bit of additional explanation (select the text with your cursor to read it, as it’s black text on a black background):

While running the test, you may have noticed that for both the first and the second test, you had to dismiss the onboarding. You can solve this by duplicating the code, but I solved this by adding a method that I annotated with ‘@Before’.
Methods that are annotated with ‘@Before’ are run before each single test. So if we’ve got 5 tests, the onboarding will be dismissed before running the code in those tests, without the need to duplicate code.


Continue with part 4. Automating RecyclerViews