Android Espresso

Filed Under: Android

In this tutorial, we’ll be discussing Espresso Testing Framework for Testing the UI of Android Applications. We’ll be creating a basic login application with a few espresso tests.

Android Espresso

We’ve already discussed Android Unit Testing before.

Testing is an important area for building any product.

It helps us in:

  • Detecting bugs/cases not covered by our code.
  • Keep manual user testing to minimal. No need to test the earlier features again and again manually.
  • Building robust applications with end cases covered.

Testing in Android majorly involves two types:

  • Unit Tests
  • Instrumentation Tests

Espresso comes under the second type. It is used to do automated UI testing by writing short and concise Android UI tests.

Let’s look at the components of the Espresso Instrumentation Testing Framework.

ViewMatchers, ViewActions, ViewAssertions

  • ViewMatchers – allows us to find a view in the view hierarchy.
  • ViewActions – allows us to perform automated actions on the view. Such as clicks etc.
  • ViewAssertions – allows us to assert the state of the view.

A basic skeleton code of an Espresso Test is:


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

Inside the onView we look for the id of the View.

It can be done using methods like:

  • withId() – Pass the unique id
  • withText() – Pass the text of the view. It searches for a view with the specified text.

Inside perform we pass the action to be done on the View. Example:

  • click() – Clicks the view passed in onView.
  • typeText() – Pass the string to be entered in the View. This is especially used in EditText.
  • replaceText() – Replaces the current text with the string that’s passed.
  • closeSoftKeyboard() – Dismisses the keyboard.

Inside check we assert the states mainly using methods:

  • matches
  • doesNotExist
We can test the view hierarchy using Espresso tests as well.

For example we can Position View Assertions in the following ways:


onView(withId(R.id.textView)).check(isRightOf(withText("Hello World")));
onView(withId(R.id.textView)).check(isBelow(withText("Hello World")));

Other methods for position assertions are:

isLeftAlignedWith
isAbove
isTopAlignedWith

Hamcrest Matchers are very powerful matchers.
The following examples show that:


onView(withText(startsWith("Hello"))).perform(click()); 
onView(allOf(withId(R.id.textView),isDisplayed()));
onView(allOf(withId(R.id.textView),hasLinks()));

The last one checks whether the whole TextView is a link.

In the following section, we’ll create an android application and write our first espresso tests.

Our android application would contain a basic login form with a username, phone number and confirm number fields. On pressing the Login Button, if the phone number fields don’t match we will show a Toast message.
If everything is fine, we’ll great the user with a Hello.

Getting Started With Espresso Tests

Add the following dependencies in the build.gradle file:


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

Add the following in the defaultConfig block :


android{
...
defaultConfig{
...
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
...
}

AndroidJUnitRunner is the instrumentation runner. It is the entry point into running the Android Tests.

Following is how our app’s build.gradle file looks :

android espresso test build gradle

Make sure you disable the Animations in your Android device before running Espresso tests.
Otherwise, the default screen animations may interfere with the tests. You can disable them from the Developer Options as shown below:

android espresso developer options

Project Structure

android espresso test project structure

Code

The code for the activity_main.xml layout is given below:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:layout_margin="8dp"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/inUsername"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Username" />


    <EditText
        android:id="@+id/inNumber"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Number"
        android:inputType="number" />

    <EditText
        android:id="@+id/inConfirmNumber"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Confirm Number"
        android:inputType="number" />


    <Button
        android:id="@+id/btnLogin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="LOGIN" />


    <TextView
        android:id="@+id/txtLoginResult"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:gravity="center" />

</LinearLayout>

The code for the MainActivity.java class is given below:


package com.journaldev.androidexpressobasics;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {


    EditText inUsername, inNumber, inConfirmNumber;
    Button btnLogin;
    TextView txtLoginResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        inUsername = findViewById(R.id.inUsername);
        inNumber = findViewById(R.id.inNumber);
        inConfirmNumber = findViewById(R.id.inConfirmNumber);

        btnLogin = findViewById(R.id.btnLogin);
        txtLoginResult = findViewById(R.id.txtLoginResult);


        btnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {


                if (TextUtils.isEmpty(inNumber.getText()))
                    Toast.makeText(getApplicationContext(), R.string.number_empty, Toast.LENGTH_SHORT).show();
                else if (!(inNumber.getText().toString().equals(inConfirmNumber.getText().toString())))
                    Toast.makeText(getApplicationContext(), R.string.toast_error, Toast.LENGTH_SHORT).show();
                else if (inUsername.getText().toString().trim().length() == 0)
                    Toast.makeText(getApplicationContext(), R.string.username_empty, Toast.LENGTH_SHORT).show();
                else
                    txtLoginResult.setText("Hello " + inUsername.getText().toString().trim());

            }
        });
    }
}

The code for the strings.xml file is given below:

android espresso test project structure

Writing Espresso Tests

Espresso tests are written inside src | androidTest folder files.
By default, ExampleInstrumentedTest.java is created with a default test.

Let’s write one test inside it:


package com.journaldev.androidexpressobasics;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static android.support.test.espresso.Espresso.onView;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.*;

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {


    public static final String USERNAME_TYPED = "Anupam";

    public static final String LOGIN_TEXT = "Hello Anupam";

    @Test
    public void useAppContext() {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();
        assertEquals("com.journaldev.androidexpressobasics", appContext.getPackageName());
    }

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

    @Test
    public void loginClickedSuccess() {
     
        onView(withId(R.id.inUsername))
                .perform(typeText(USERNAME_TYPED));
        onView(withId(R.id.inNumber))
                .perform(typeText("12345"));
        onView(withId(R.id.inConfirmNumber))
                .perform(typeText("12345"));

        onView(withId(R.id.btnLogin)).perform(click());
        onView(withId(R.id.txtLoginResult)).check(matches(withText(LOGIN_TEXT)));
    }
}

@Rule annotation is used to launch the MainActivity.
Inside loginClickedSuccess we wrote a successful login test case.

Now let’s write two more tests in which we will test whether the Toast is shown correctly or not.

Add the following methods inside the above class:


@Test
    public void shouldShowToastError() {
        onView(withId(R.id.inUsername))
                .perform(typeText(USERNAME_TYPED));
        onView(withId(R.id.inNumber))
                .perform(typeText("123456"));
        onView(withId(R.id.inConfirmNumber))
                .perform(typeText("12345"), closeSoftKeyboard());

        onView(withId(R.id.btnLogin)).perform(click());
        onView(withText(R.string.toast_error)).inRoot(withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));
    }

    @Test
    public void shouldShowToastUsernameEmpty() {
        onView(withId(R.id.inNumber))
                .perform(typeText("12345"));
        onView(withId(R.id.inConfirmNumber))
                .perform(typeText("12345"));

        onView(withId(R.id.btnLogin)).perform(click());
        onView(withText(R.string.username_empty)).inRoot(withDecorView(not(is(mActivityRule.getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));
    }

The last line is used to check whether Toast is displayed with the correct string or not.

To run the espresso test. Right click on the ExampleInstrumentationTest.java and press run to start the automated test on the emulator.

Following is the output of the application in action:

android espresso test output

Following is the output in the console while the above automated UI test was running:

android espresso test console output

Espresso Test Recording

You can record Espresso tests manually as well:

android espresso test record

It will automatically create a new file with the espresso tests containing the manual clicks and actions you did in the device.

This brings an end to this tutorial. You can download the project from the link below:

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages