let’s dev GmbH & Co. KG - The brand for groundbreaking custom software

Blog

Top!

let’s dev | Scroll to top
let’s dev | Scroll to next content item

Hello

let’s dev | Scroll to previous content item
let’s dev Blog | Introduction to User Interface (UI) Testing with Espresso
by Raphael
07. Juni 2018

Introduction to User Interface (UI) Testing with Espresso

Ensuring software quality with the help of tests is an important topic in software development. This allows errors to be detected and eliminated at an early stage. Errors that arise in the further course of programming can also be identified in this way.

In Android, a basic distinction is made between two types of tests: unit tests and instrumentation tests. In the following article, user interface tests (UI tests), a type of instrumentation tests, will be examined in more detail. However, it should be emphasized at this point that only by using all types of tests can the quality assurance of the code be optimally guaranteed.

UI-Tests

In classic programming, the codebase is mainly covered with unit and integration tests. This should also be done in programming for Android. However, this article focuses on ensuring an error-free user interface (UI).

On Android, UI tests can be mapped natively using the Espresso framework. However, there are also other test frameworks such as Robotium, which can also be used to create UI tests for Android.

Espresso

Espresso is a framework from Google that was developed specifically for testing Android user interfaces. It was introduced at the Google Test Automation Conference in April 2013 and made available to developers in October of the same year. Espresso makes it possible to write native UI tests and thus directly access code from the development.

Setup

Since Espresso is a native framework, the setup is comparatively simple. The first step is to add the latest versions of the following dependencies to the application's build.gradle:

androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test:rules:1.0.2'

Also, the test runner must be added to the defaultConfig section of build.gradle:

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

Afterwards, the setup is complete and you can start writing the tests.

Tests with Espresso

In this example, we will test a small application that consists of two screens. The first screen contains two buttons and a TextView, the second screen contains a RecyclerView and a FloatingActionButton (FAB). Above the list there should be a TextView. Two fragments in an activity were used to implement the screens.
 The list was created using a RecyclerView.

If you click on the left button in the first screen, the counter in the TextView is incremented.

Figure 1: Clicking the right button opens the second screen

Figure 1: Clicking the right button opens the second screen

Figure 2: In the second screen, an element is to be added to the list when the FAB is clicked. An element of the list consists of a TextView and a checkbox.

Figure 2: In the second screen, an element is to be added to the list when the FAB is clicked. An element of the list consists of a TextView and a checkbox.

The TextView should contain the element with number, i.e. "Item 0" for the first element, "Item 1" for the second element and so on. In addition, the number of elements that are selected and the number of the total elements should be displayed in the TextView above the list. This should be displayed as follows: "2/5 elements selected". After clicking on the "Back" button in the upper left corner, the view returns to the first screen.

Figure 3

Figure 3

UI tests are now defined for this application. The first step is to create a rule for the activity used.

public class MainActivityTestRule extends ActivityTestRule{

    public MainActivityTestRule(Class activityClass) {

        super(activityClass);
    }
}

The standard ActivityTestRule can also be used here. However, since something will be overwritten at this point later, a separate rule should be created for the MainActivity.

The first espresso test can now be written. For this you create a class in the "androidTest" folder, in which integration tests are searched for by default. Since no extremely large test class is needed, one class can be created for the first screen and one for the second screen. So that the rule can be used in both, you create the "BaseTest" class, from which both test classes can inherit. Then a new instance of the rule can be created:

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

In the next step, a test class can be created for the first screen. This inherits from "BaseTest". With the annotation @Test a method is now marked that represents a test. It is recommended to name the test methods in a meaningful way, so that it is immediately obvious what exactly is being tested. For UI tests, the following notation is recommended:

test_wasSollImTestPassieren_WasWirdErwartet()

This approach can lead to the names of the test methods becoming very long, but allows the developer to quickly see what is being tested in this method even without documentation.

The first test is to check if the UI was initialized with the correct values:

@Test
public void test_uiHasSetupCorrectly_AllViewsShouldBeVisibleAndHaveCorrectValues(){
    onView(withId(R.id.fragment_button_counter_text_view)).check(
                matches(withText("Number of Button Clicks: 0")));
    onView(withId(R.id.fragment_button_counter_button)).check(matches(withText("Count")));
    onView(withId(R.id.fragment_button_fragment_change_button)).check(
                matches(withText("Change Fragment")));
}

With onView() the view to be tested is selected. The id is the "android:id" specified in the XML layout file for a UI element. Then a matcher is expected via check(). In our case, only the string is compared in each case. Here it is better to use string resources instead of fixed strings. This has two advantages:

For the test application, however, it is recommended to stay with fixed strings.

Now it is ensured that the screen has been built up properly. The next function of the first screen is to click on the left button and count up the counter in the text field. In addition to checking an element as in the previous test, clicking the button is also added here, i.e. an action:

@Test
public test_ButtonClickUpdatesCounter_CounterCountsUp(){
    onView(withId(R.id.first_screen_text_view)).check(matches(withText(“0 Klicks”)));
    onView(withId(R.id.first_screen_left_button)).perform(click());
    onView(withId(R.id.first_screen_text_view)).check(matches(withText(“1 Klicks”)));
}

First it checks if the initial value is correct, then the onView(withId()) selects the left button and perform() selects an action to be executed on the object. In our case, the action is click().

Subsequently, it should be checked whether the counter has been incremented. A distinction between singular and plural for one or more elements was omitted here. To ensure that this also works more than once, another test is added in which the button is clicked ten times:

@Test
public test_ButtonClickUpdatesCounter_CounterCountsUp(){
    onView(withId(R.id.first_screen_text_view)).check(
            matches(withText(“0 Klicks”)));
    for(int i = 0; i<10 ; i++{
                onView(withId(R.id.first_screen_left_button)).perform(click());
    }
    onView(withId(R.id.first_screen_text_view)).check(
            matches(withText(“10 Klicks”)));
}

The click() action is now executed ten times in a loop. Subsequently, the check is made whether it was counted correctly.

There is a second action in the first screen, whose correct execution is to be ensured by means of a UI test. Clicking on the right button should open the second screen.

To test whether the view has actually opened, you can check whether a layout element of the second screen is visible. Alternatively, you could also use the title of the toolbar or another unique feature of the view. In our case, the FAB was selected in the second screen:

@Test
public test_ButtonClickUpdatesCounter_CounterCountsUp(){
    onView(withId(R.id.first_screen_right_button)).perform(click());
    onView(withId(R.id.second_screen_floating_action_button)).check(matches(isDisplayed()));
}

After clicking on the button in the first screen, the FAB can be found via the layout ID and it is checked whether the button is displayed.

Now all functions of the first screen are covered. The tests for the second screen are created in the second test class. It is noticeable that all tests must first navigate to the second screen before they can be executed. Since this is true for all tests in this test class, the @Before annotation can be used here and executed before each test can be executed. Otherwise, a method would have to be called in each test that navigates to the second screen.

@Before
public void setUp(){
	onView(withId(R.id.first_screen_right_button)).perform(click());
}

Now you can start testing the second screen, the following should be tested:

Clicking the FAB adds an item to the list, the TextView in the added item contains the correct text, and the TextView above the list has updated correctly.

To check the UI in the basic state, only the content of the TextView above the list and the visibility of the FAB need to be checked here.

Now to the first test, which contains an element of a RecyclerView. The click on the FAB can be realized analogously to the clicks on the buttons in the first screen. To examine an element of a RecyclerView, you need your own ViewMatcher. With custom ViewMatchers, you can examine almost any element in Espresso. Often there are already implementations of ViewMatchers for UI elements. For the RecyclerView ViewMatcher was used.

Then, a method withRecyclerView() is introduced in the "BaseTest" class to call the watcher like the standard Espresso Matcher.

public static RecyclerViewMatcher withRecyclerView(final int recyclerViewId) {
    return new RecyclerViewMatcher(recyclerViewId);
}

Now the elements of the RecyclerView can be tested. Here you should get into the habit of scrolling to the index of the element before testing it. This is necessary because Espresso can only test an element that is displayed on the screen. To make the scrolling of the RecyclerView possible, we must now include the "espresso-contrib" repository. This contains additional possibilities UI elements, to match or interact with them. For our test we need these RecyclerViewActions:

@Test
public void test_clickFAB_itemIsAddedToListTextViewUpdated() {
    onView(withId(R.id.list_fragment_floating_action_button)).perform(click());
    onView(withId(R.id.list_fragment_recycler_view)).perform(RecyclerViewActions.scrollToPosition(0));
    onView(withRecyclerView(R.id.list_fragment_recycler_view).atPosition(0)).check(
            matches(hasDescendant(withText("Item 0"))));
    onView(withId(R.id.list_fragment_title_text_view)).check(matches(withText("0/1 items selected")));
}

After clicking the FAB, you scroll to the index of the list to be checked using the RecyclerViewAction scrollToPosition(index). While this is not strictly necessary here, since the first element of the List is normally always visible, but this way the step is not forgotten in other cases. Additionally one should consider that the device, with which one develops, is not necessarily the same device on which the tests must be executed.

Then, the matcher can be used to access the list element at location 0 and check whether one of its subviews contains the correct text. Since only one element with the corresponding text is provided here, this is sufficient.

The next step is to select a checkbox and then check whether the text above the list changes correctly:

@Test
public void test_addElementSelectElementCheckBox_shouldRefreshTextViewCorrectly() {
    onView(withId(R.id.list_fragment_floating_action_button)).perform(click());
    onView(withId(R.id.list_fragment_recycler_view)).perform(RecyclerViewActions.scrollToPosition(0));
    onView(withId(R.id.list_fragment_recycler_view)).perform(
            RecyclerViewActions.actionOnItemAtPosition(0, clickChildViewWithId(R.id.row_list_item_checkbox)));
    onView(withId(R.id.list_fragment_title_text_view)).check(matches(withText("1/1 items selected")));
}

A new feature is the click on the checkbox. Since the checkbox is located in a list element, you can use a RecyclerViewAction here again. With actionOnItemAtPosition an index and a ViewAction can be provided:

public class ChildClickViewAction {
    public static ViewAction clickChildViewWithId(final int resourceId) {
        return new ViewAction() {
            @Override
            public Matcher getConstraints() {
                return null;
            }

            @Override
            public String getDescription() {
                return "Click on a child view with specified id.";
            }

            @Override
            public void perform(UiController uiController, View view) {
                View v = view.findViewById(resourceId);
                v.performClick();
            }
        };
    }
}

In this case, the corresponding element within the given view is searched for via the ResourceId and this view is clicked on. After clicking on the checkbox, it is checked whether the TextView above the list has also been updated accordingly.

No new concepts are needed for the latest tests, so they are not explicitly listed here. However, both tests can be viewed via Github.

Finally, it should be checked whether the navigation via the "Back" button in the upper left corner works. Here we have to pay attention to a special feature: Unfortunately, Espresso cannot access the button with the id. At least not if it was created with setHomeAsUpEnabled(true). Instead, a description is used for this:

@Test
public void test_pressBackButton_shouldReturnToButtonFragment(){
    onView(withContentDescription(R.string.abc_action_bar_up_description)).perform(click());
    onView(withId(R.id.fragment_button_counter_text_view)).check(matches(isDisplayed()));
}

In order to click on the "Back" button, the description must first be determined. This has a fixed resource id, i.e. this test also works language-independently. The last step checks whether the TextView is displayed in the first screen.

Now the correct functioning of the app was almost completely ensured with UI tests. However, one special case should still be taken into account. So far, the tests would only run when the test device is unlocked. When the screen is off or the device is locked, all tests fail because the activity is not found. Therefore, the device should be unlocked and the screen should be turned on before the tests are executed. For this, the test device must not have a lock like a PIN.

For this we need to go back into our MainActivityTestRule class and implement the interface ActivityLifecycleCallback. This allows us to implement the onActivityLifecycleChanged() method.

@Override
public void onActivityLifecycleChanged(Activity activity, Stage stage) {
    if (stage == Stage.PRE_ON_CREATE) {
        activity.getWindow()
                .addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                        | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
                        | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON);
    }
}

Here you check if the activity is in PRE_ON_CREATE state, that is before it is created, and try to turn on the screen and unlock the device.

Additionally, the beforeActivityLaunched method must be overridden to set the listener. This way the tests will be executed even if the screen is turned off.

@Override
protected void beforeActivityLaunched() {
    super.beforeActivityLaunched();
    ActivityLifecycleMonitorRegistry.getInstance().addLifecycleCallback(this);
}

In conclusion, Espresso is a valuable tool in app development for Android, and its use in conjunction with other types of testing is highly recommended.

The entire application including all tests described here is available on GitHub.

Image credits:

  • GettyImages: RapidEye

More articles from our blog

let’s dev Blog | Sketch, Figma & Co. - We take a look at the most popular UI und Prototyping Tools in 2021

Corporate

Sketch, Figma & Co. - We take a look at the most popular UI und Prototyping Tools in 2021

by Ellen

2021-07-15

Read more
let’s dev Blog | Tailwind: An innovative project for the future use of old wind turbines

Corporate

Tailwind: An innovative project for the future use of old wind turbines

by Karl

2021-06-24

Read more
let’s dev Blog |

Corporate

by Julian

2021-06-10

Read more
let’s dev Blog | Smart Prognosis of Energy with Allocation of Resources

Corporate

Smart Prognosis of Energy with Allocation of Resources

by Karl

2021-02-18

Read more
let’s dev Blog | Dasoman - Data-Sovereignty-Manager

Corporate

Dasoman - Data-Sovereignty-Manager

by Karl

2021-01-11

Read more
let’s dev Blog | We look back on the past months - And wish all the best for the coming year 2021!

Corporate

We look back on the past months - And wish all the best for the coming year 2021!

by Julian

2020-12-17

Read more
let’s dev Blog | iOS User Interface Tests

Technical

iOS User Interface Tests

by Nicolas

2020-11-12

Read more
let’s dev Blog | Adobe Max - Online for the first time

Corporate

Adobe Max - Online for the first time

by Julia

2020-10-29

Read more
let’s dev Blog | CAN2BLE

Technical

CAN2BLE

by Raphael

2020-09-24

Read more
let’s dev Blog | Mensch und Computer 2020 - Digital Change in the Flow of Time

Corporate

Mensch und Computer 2020 - Digital Change in the Flow of Time

by UX Team

2020-09-18

Read more
let’s dev Blog | Neumorphism – A new era of user interface design?

Technical

Neumorphism – A new era of user interface design?

by Julian

2020-08-13

Read more
let’s dev Blog | UX Research Part 3 - UX Methods

Technical

UX Research Part 3 - UX Methods

by Elena

2020-05-28

Read more
let’s dev Blog | UX Research Part 2 - What is UCD and what does User Research have to do with it?

Technical

UX Research Part 2 - What is UCD and what does User Research have to do with it?

by Elena

2020-04-23

Read more
let’s dev Blog | go-digital promotes establishment of home office workstations

Corporate

go-digital promotes establishment of home office workstations

by Karl

2020-03-19

Read more
let’s dev Blog | Google Passes - Card Management on Android Devices

Technical

Google Passes - Card Management on Android Devices

by Michelle

2020-03-12

Read more
let’s dev Blog | 100% code coverage in software testing - a reasonable goal?

Technical

100% code coverage in software testing - a reasonable goal?

by Raphael

2020-03-06

Read more
let’s dev Blog | Swift UI - Simple and fast implementation of user interfaces

Technical

Swift UI - Simple and fast implementation of user interfaces

by Tobias

2020-03-02

Read more
let’s dev Blog | In dialog with the business juniors - Exciting insights into business start-ups and digital transformation

Corporate

In dialog with the business juniors - Exciting insights into business start-ups and digital transformation

by Julian

2020-02-27

Read more
let’s dev Blog | Simplified testing of iOS push notifications in the simulator with Xcode 11.4

Technical

Simplified testing of iOS push notifications in the simulator with Xcode 11.4

by Manuel

2020-02-26

Read more
let’s dev Blog | National meeting of the consortium of the SPEAR research project at let's dev in Karlsruhe

Corporate

National meeting of the consortium of the SPEAR research project at let's dev in Karlsruhe

by Karl

2020-01-27

Read more
let’s dev Blog | UX Research Part 1 - Why User Research is so important

Technical

UX Research Part 1 - Why User Research is so important

by Elena

2020-01-23

Read more
let’s dev Blog | Dark Mode

Technical

Dark Mode

by Elisa

2020-01-09

Read more
let’s dev Blog | We wish you a Merry Christmas - And a Happy New Year!

Corporate

We wish you a Merry Christmas - And a Happy New Year!

by Julian

2019-12-20

Read more
let’s dev Blog | Exchange on the topic of digitization with the Business Club Luxembourg at the Embassy of Luxembourg in Berlin

Corporate

Exchange on the topic of digitization with the Business Club Luxembourg at the Embassy of Luxembourg in Berlin

by Karl

2019-12-17

Read more
let’s dev Blog | DaSoMan at the Internet+ Expo in Foshan (China)

Corporate

DaSoMan at the Internet+ Expo in Foshan (China)

by Karl

2019-12-13

Read more
let’s dev Blog | Google Play Console: Pre-Launch Reports

Technical

Google Play Console: Pre-Launch Reports

by Fabian

2019-12-11

Read more
let’s dev Blog | DevFest 2019 in Hamburg

Technical

DevFest 2019 in Hamburg

by Julian

2019-12-05

Read more
let’s dev Blog | Vernissage digital art in the media theater of the Humboldt University Berlin

Corporate

Vernissage digital art in the media theater of the Humboldt University Berlin

by Karl

2019-11-21

Read more
let’s dev Blog | World Usability Day 2019 in Karlsruhe - let's dev supports as main sponsor

Corporate

World Usability Day 2019 in Karlsruhe - let's dev supports as main sponsor

by Aileen

2019-11-11

Read more
let’s dev Blog | Gutted - Open Day at the Alter Schlachthof Karlsruhe 2019

Corporate

Gutted - Open Day at the Alter Schlachthof Karlsruhe 2019

by Julian

2019-09-26

Read more
let’s dev Blog | Mensch und Computer 2019 - Conference on User Experience and Usability in Hamburg

Corporate

Mensch und Computer 2019 - Conference on User Experience and Usability in Hamburg

by Elena

2019-09-17

Read more
let’s dev Blog | Business and Enterprise App Distribution on iOS

Technical

Business and Enterprise App Distribution on iOS

by Aileen

2019-08-05

Read more
let’s dev Blog | Digital Transformation - Chances and Challenges in the Automotive Industry, Agriculture and New Technologies

Corporate

Digital Transformation - Chances and Challenges in the Automotive Industry, Agriculture and New Technologies

by Karl

2019-07-17

Read more
let’s dev Blog | let's dev supports runners at the 7th KIT Championship

Corporate

let's dev supports runners at the 7th KIT Championship

by Karl

2019-07-05

Read more
let’s dev Blog | Automated testing of C++ code with Google Test and Google Mock - Part 2

Technical

Automated testing of C++ code with Google Test and Google Mock - Part 2

by Arne

2019-06-13

Read more
let’s dev Blog | Apple WWDC 2019: These are the highlights of the keynote

Technical

Apple WWDC 2019: These are the highlights of the keynote

by Nicolas

2019-06-05

Read more
let’s dev Blog | App Builders 2019

Technical

App Builders 2019

by Nicolas

2019-05-23

Read more
let’s dev Blog | Official opening of the Consolidation and Expansion Center (FUX)

Corporate

Official opening of the Consolidation and Expansion Center (FUX)

by Helena

2019-04-15

Read more
let’s dev Blog | Delegation from Nottingham to visit the Alter Schlachthof in Karlsruhe

Corporate

Delegation from Nottingham to visit the Alter Schlachthof in Karlsruhe

by Helena

2019-04-14

Read more
let’s dev Blog | The time has come: We are moving!

Corporate

The time has come: We are moving!

by Helena

2019-03-26

Read more
let’s dev Blog | Automated testing of C++ code with frameworks - part 1

Technical

Automated testing of C++ code with frameworks - part 1

by Arne

2019-02-20

Read more
let’s dev Blog | The app in the Google Play Store

Technical

The app in the Google Play Store

by Elisa

2019-01-24

Read more
let’s dev Blog | „UX Day“ 2018

Corporate

„UX Day“ 2018

by Aileen

2018-12-17

Read more
let’s dev Blog | let's dev supports SG Siemens volleyball players from Karlsruhe

Corporate

let's dev supports SG Siemens volleyball players from Karlsruhe

by Helena

2018-12-04

Read more
let’s dev Blog | SMEs shape digitalization - SME Conference 2018

Corporate

SMEs shape digitalization - SME Conference 2018

by Helena

2018-11-12

Read more
let’s dev Blog | Apple Wallet

Technical

Apple Wallet

by Maik

2018-10-26

Read more
let’s dev Blog | „Mensch und Computer“ 2018

Corporate

„Mensch und Computer“ 2018

by Judith

2018-09-24

Read more
let’s dev Blog | State Design Pattern in Android

Technical

State Design Pattern in Android

by Thomas

2018-09-17

Read more
let’s dev Blog | let's dev is an authorized consulting company in the „go-digital“ funding program

Corporate

let's dev is an authorized consulting company in the „go-digital“ funding program

by Helena

2018-09-01

Read more
let’s dev Blog | App Design & Development Conference 2018

Corporate

App Design & Development Conference 2018

by Helena

2018-08-14

Read more
let’s dev Blog | iOS 12: The top new features at a glance

Technical

iOS 12: The top new features at a glance

by Nicolas

2018-07-17

Read more
let’s dev Blog | let's dev at CEBIT

Corporate

let's dev at CEBIT

by Karl

2018-06-11

Read more
let’s dev Blog | The app in the Apple App Store: what information is needed?

Technical

The app in the Apple App Store: what information is needed?

by Aileen

2018-04-27

Read more
let’s dev Blog | Smart Pointer in C++

Technical

Smart Pointer in C++

by Matthias

2018-04-01

Read more
let’s dev Blog | User interface design for iPhone X: all innovations at a glance

Technical

User interface design for iPhone X: all innovations at a glance

by Helena

2018-02-07

Read more
let’s dev Blog | WebVR - Virtual Reality Experience in the Browser with the A-Frame Framework

Technical

WebVR - Virtual Reality Experience in the Browser with the A-Frame Framework

by Judith

2018-01-10

Read more
let’s dev Blog | Deutsche Bahn Open Data Hackathon

Corporate

Deutsche Bahn Open Data Hackathon

by Karl

2015-03-31

Read more
let’s dev Blog | Blur effects under iOS 7

Technical

Blur effects under iOS 7

by Katja

2014-04-24

Read more
let’s dev Blog | Beyond App Store - iOS application distribution

Technical

Beyond App Store - iOS application distribution

by Karl

2012-08-27

Read more
let’s dev Blog | Front-end architecture - Model View Presenter and Message Bus

Technical

Front-end architecture - Model View Presenter and Message Bus

by Karl

2011-03-08

Read more