Getting started with Espresso Android and Robot pattern

Robert Gorter
8 min readNov 2, 2022

If you want to start your first test automation using Espresso on an Android app, but you have no idea how, this is the tutorial for you!

It will help you set up your first Espresso test, and dive into Robot pattern as a proper test design pattern.

Espresso

Espresso is Google’s own test automation framework to write Android UI tests. The framework is easy to setup and maintain. There is a lot of documentation and community support available. And the test execution is rather fast, compared to non-integrated frameworks such as Appium.

The basics of Espresso consists of four main components:

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

A must-have for using Espresso as a beginner is the Espresso cheat sheet.
This cheat sheet contains most available instances of Matcher, ViewAction, and ViewAssertion.

Espresso cheat sheet

Getting started

  • Get an app
  • Add Espresso dependencies and Runner
  • Add your first test
  • Test a different activity
  • Setup a decent framework

Get an app

To get started you would need an app. Luckily for this tutorial I provide one for you. You can find it here: https://github.com/jacketti/My-Quiz-App/tree/getting-started-espresso

You can either download or checkout the project and open it in Android Studio. When you do it will look something like this:

Quiz app in Android Studio

Add Espresso dependencies and Runner

The next thing that you will need to do is add Espresso to your build.gradle. This file is located in the gradle scripts. When your using the Android project layout, there are two build.gradle files next to eachother. One is actually in the project root folder and its configuration settings apply to every module in the project. The second, is for the specific app module, it is possible to have multiple modules inside the same project. In this case we will need the gradle script for this specific app module.

Here we will add the espresso dependencies to the dependencies. At the time of writing the versions will be as such:


androidTestImplementation ‘androidx.test.espresso:espresso-core:3.4.0’
androidTestImplementation ‘androidx.test:runner:1.4.0’
androidTestImplementation ‘androidx.test:rules:1.4.0’

And make sure that in the same file, in the defaultConfig the following line is present:

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

hit the Sync Now button and wait for the build to complete.

Add your first test

In the project’s java folder you will see three folders named com.gorter.myquizapp , the first one contains all the code for the app, the second one is for androidTest (the UI tests that we will use) and the third one is for the unit tests.

Inside the second folder, you will see the ExampeInstrumentedTest, ignore that one for now, we will start writing our first tests from scratch. It is possible to record an Espresso test, the recording will launch the app and all the actions that you perform during the recording will be automatically saved into a test script, but that is not part of this tutorial. Instead, right-click on the folder select ‘New’ — ‘Kotlin class/file’ and name it MyFirstTest

MyFirstTest class

Next we will need to tell the project that this class is to be run with the Android JUnit class, this is a test runner that lets you run instrumented tests on Android devices. Add this line above the class name.

@RunWith(AndroidJUnit4::class)

An app usually has different screens and that means different activities. For our test we will have to declare which activity we will be using. That we can do by using the JUnit Rule, inside the class put this code:

@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)

As I mentioned, apps can have different screens and different activities. This rule uses the MainActivity class, which is usually the first screen used in an app, and this app is no exception. You can also check out the code for the MainActivity.kt.

But first let’s write our first test. Inside the class we will need the @Test annotation, and then the test function. The simplest test I could imagine for this example is that there is a text shown on the screen.

@Test
fun titleIsShown() {
onView(withText("Quiz App!")).check(matches(isDisplayed()))
}

Make sure to import all the necessities. By now it should look like this:

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

On the left hand side of the screen there should be a ‘play’ button of sorts. If you press that now the app will launch and your first test will be executed.

first test executed

In this first test we are checking that a certain text is displayed anywhere on the screen. Not a very solid test I must say. Let’s write a test that fills in the name, presses the start button, and checks that the next screen is shown.

For this we will need a new test, so start with the @Test annotation, let’s call this one, canEnterName. This time we will not use the onView(withText("Quiz App!")) but instead, we will use the view ID. Open the activity_main.xml this is the xml file that makes up the design for the main activity screen. For this test we will use the input field, and as we can see in the XML it has an ID called et_name

Have a look at your espresso cheat sheet, and figure out how to write your name in this field. The next steps in the test will be to hit the start button and verify that you’re on the next screen.

@Test
fun canEnterName() {
onView(withId(R.id.et_name)).perform(typeText("Robert"))
closeSoftKeyboard()
onView(withId(R.id.btn_start)).perform(click())
onView(withId(R.id.tv_question)).check(matches(isDisplayed()))
}

This is the test that I wrote for this scenario. I use onView(withId(R.id.et_name) to find the input field as it was listed in the XML, on that view I perform the typeText with a String that is my name. In order to access the Start button I first need to get rid of the keyboard, so I use the Espresso API to close the softKeyboard, then I click on the Start button, with the ID I also found in the XML. And on the next screen I select an element from that screen (activity_quiz_questions.xml) and verify that it is displayed.

Test a different activity

If you run the app on your phone or emulator, you will see that after you have entered your name, you enter a quiz and can answer 10 questions before seeing the result screen. Now lets say you want to test the results screen, will your test enter a name, answer all 10 questions and then finally check the results screen? No! This is Espresso, we can do it a lot quicker. We will create a new class, launch the results screen and run our tests immediately.

We can create a new class, e.g. ResultScreenTest, and instead of launching the MainActivity we can launch the ResultActivity . This way you don’t have to go trough the entire app to test one screen, but you can launch the screen and perform your tests immediately.

Setup a decent framework

A small project like this one is easy to maintain, but the bigger the project gets, the harder it will be. The best way to set up a project is to make use of a proper design pattern. In web automation the most common one I know is the Page Object Model. In app automation the most common one is the Robot Pattern.

In the Robot Pattern each screen will have its own Robot with interactions performed on that specific screen. Also there can be a Base Robot with generic interactions that you can use on several screens, like checking for a certain view, or a generic way to fill in an input field and closing the keyboard.

We will start refactoring our tests to fit in the Robot Pattern.

The first step will be to create a new package that will contain the robots, so let’s call it robots . In here create a class called BaseTestRobot, this will have some generic functions that we can use on all the screens. So far we’ve created a test that fills in the name, and clicks on a button. We can make a reusable function that can select a view, enters a text and closes the keyboard. And another function that clicks on a certain view and lastly a function to assert a view is displayed.

fun enterTextOnViewWithId(@IdRes viewId: Int, text: String) {
onView(withId(viewId)).perform(ViewActions.typeText(text))
closeSoftKeyboard()
}
fun clickViewWithId(@IdRes viewId: Int) {
onView(withId(viewId))
.check(matches(isDisplayed()))
.perform(click())
}

fun viewWithIdIsDisplayed(@IdRes viewId: Int) {
onView(withId(viewId))
.check(matches(isDisplayed()))
}

Next create the MainActivityRobot it’s a new class in the robots package. It will need to extend the BaseTestRobot, and make each function return the object of itself with apply. Wait, what?

fun main(func: MainActivityRobot.() -> Unit) =
MainActivityRobot().apply{ func() }
class MainActivityRobot: BaseTestRobot() {
}

Now we can start writing functions for this specific screen, that can use the functions of the BaseTestRobot and can then make a easy to read and maintain test. The functions on this screen could be to enter the name, and to press start.

fun enterName(name: String) {
enterTextOnViewWithId(R.id.et_name, name)
}

fun pressStart(){
clickViewWithId(R.id.btn_start)
}

As a last step in your test, we need to verify that you are on the next screen, so we need a new Robot, create QuizQuestionsRobot . It’s basically the same process as before, so I will just show it here:

package com.gorter.myquizapp.robots
import com.gorter.myquizapp.R

fun quizQuestions(func: QuizQuestionsRobot.() -> Unit) =
QuizQuestionsRobot().apply{ func() }
class QuizQuestionsRobot: BaseTestRobot() {

fun isOnQuestionsScreen() {
viewWithIdIsDisplayed(R.id.tv_question)
}
}

Head over to your test class, and write the new test. Since we can now use the Robots the test can be written differently.

@Test
fun canEnterName() {
onView(withId(R.id.et_name)).perform(typeText("Robert"))
closeSoftKeyboard()
onView(withId(R.id.btn_start)).perform(click())
onView(withId(R.id.tv_question)).check(matches(isDisplayed()))
}

@Test
fun canEnterNameAndSeeNextScreen() {
main {
enterName("Robert")
pressStart()
}
quizQuestions {
isOnQuestionsScreen()
}
}

This can be done for all the tests, and all the screens. If you’d like to see my solution for the project check out this branch

--

--

Robert Gorter

I'm a mobile app tester. I build test automation with XCUITEST, Espresso, Appium and Rest Assured