This tutorial describes how to developer user interface tests for Android applications with the Espresso testing framework. This tutorial assumes that you are familiar with Android programming in general.

1. The Espresso test framework

Espresso is a testing framework for Android to make it easy to write reliable user interface tests.

Google released the Espresso framework in Oct. 2013. Since its 2.0 release Espresso is part of the Android Support Repository.

Espresso automatically synchronizes your test actions with the user interface of your application. The framework also ensures that your activity is started before the tests run. It also let the test wait until all observed background activities have finished.

It is intended to test a single application but can also be used to test across applications. If used for testing outside your application, you can only perform black box testing, as you cannot access the classes outside of your application.

Espresso has basically three components:

  • ViewMatchers - allows to find view in the current view hierarchy

  • ViewActions - allows to perform actions on the views

  • ViewAssertions - allows to assert state of a view

The case construct for Espresso tests is the following:

Base Espresso Test
onView(ViewMatcher)       (1)
 .perform(ViewAction)     (2)
   .check(ViewAssertion); (3)
1 - Finds the view
2 - Performs an action on the view
3 - Validates a assertioin

The following code demonstrates the usage of the Espresso test framework.

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;

// image more code here...

// test statement
onView(withId(R.id.my_view))            // withId(R.id.my_view) is a ViewMatcher
        .perform(click())               // click() is a ViewAction
        .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion

// new test
onView(withId(R.id.greet_button))
.perform(click())
.check(matches(not(isEnabled()));

If Espresso does not find a view via the ViewMatcher, it includes the whole view hierarchy into the error message. That is useful for analyzing the problem.

2. Making Espresso available

2.1. Installation

Use the Android SDK manager to install the Android Support Repository.

Install Android Support Repository

2.2. Configuration of the Gradle build file for Espresso

To use Espresso for your tests, add the following dependency to the Gradle build file of your app.

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    testImplementation 'junit:junit:4.12'

    // Android runner and rules support
    androidtestImplementation 'com.android.support.test:runner:0.5'
    androidtestImplementation 'com.android.support.test:rules:0.5'

    // Espresso support
    androidtestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })

    // add this for intent mocking support
    androidtestImplementation 'com.android.support.test.espresso:espresso-intents:2.2.2'

    // add this for webview testing support
    androidtestImplementation 'com.android.support.test.espresso:espresso-web:2.2.2'

}

Ensure that the android.support.test.runner.AndroidJUnitRunner is specified as value for the testInstrumentationRunner parameter in the build file of your app. Via the packagingOptions you may have to exclude LICENSE.txt, depending on the libraries you are using. The following listing is an example for that.

apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion '22.0.1'
    defaultConfig {
        applicationId "com.example.android.testing.espresso.BasicSample"
        minSdkVersion 10
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    packagingOptions {
        exclude 'LICENSE.txt'
    }
    lintOptions {
        abortOnError false
    }
}

dependencies {
    // as before.......
}

2.3. Device settings

It is recommended to turn off the animation on the Android device which is used for testing. Animations might confusing Espressos check for ideling resources.

turnanimations off

3. Exercise: A first Espresso test

3.1. Create project under test

Create a new Android project called Espresso First with the package name com.vogella.android.espressofirst. Use the Blank Template as basis for this project.

Change the generated activity_main.xml layout file to the following.

<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"
    >

    <EditText
        android:id="@+id/inputField"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/changeText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="New Button" android:onClick="onClick"/>

    <Button
        android:id="@+id/switchActivity"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Change Text" android:onClick="onClick"/>
</LinearLayout>

Create a new file called activity_second.xml.

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:text="Large Text"
        android:id="@+id/resultView" />
</LinearLayout>

Create a new activity with the following code.

package com.vogella.android.espressofirst;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class SecondActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);
        TextView viewById = (TextView) findViewById(R.id.resultView);
        Bundle inputData = getIntent().getExtras();
        String input = inputData.getString("input");
        viewById.setText(input);
    }
}

Also adjust your MainActivity class.

package com.vogella.android.espressofirst;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class MainActivity extends Activity {

    EditText editText;

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

        editText = (EditText) findViewById(R.id.inputField);
    }


    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.changeText:
                editText.setText("Lalala");
                break;
            case R.id.switchActivity:
                Intent intent = new Intent(this, SecondActivity.class);
                intent.putExtra("input", editText.getText().toString());
                startActivity(intent);
                break;
        }

    }
}

3.2. Adjust the app build.gradle

Perform the setting of the configuration of the Gradle build file for Espresso.

3.3. Create your Espresso test

package com.vogella.android.espressofirst;

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.Espresso.onView;
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.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;


@RunWith(AndroidJUnit4.class)
public class MainActivityEspressoTest {


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

    @Test
    public void ensureTextChangesWork() {
        // Type text and then press the button.
        onView(withId(R.id.inputField))
                .perform(typeText("HELLO"), closeSoftKeyboard());
        onView(withId(R.id.changeText)).perform(click());

        // Check that the text was changed.
        onView(withId(R.id.inputField)).check(matches(withText("Lalala")));
    }

    @Test
    public void changeText_newActivity() {
        // Type text and then press the button.
        onView(withId(R.id.inputField)).perform(typeText("NewText"),
                closeSoftKeyboard());
        onView(withId(R.id.switchActivity)).perform(click());

        // This view is in a different Activity, no need to tell Espresso.
        onView(withId(R.id.resultView)).check(matches(withText("NewText")));
    }
}

3.4. Run your test

Right-click on your test and select Run.

4. More on writing Espresso unit tests

4.1. Location of Espresso tests and required static imports

Espresso tests must be placed in the app/src/androidTest folder.

To simplify the usage of the Espresso API it is recommended to add the following static imports. This allows to access these methods without the class prefix.

import static android.support.test.espresso.Espresso.onView;
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.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

4.2. Using ViewMatcher

To find a view, use the onView() method with a view matcher which selects the correct view. If you are using an AdapterView use the onData() method instead of the onView() method. The The onView() methods return an object of type ViewInteraction. The onData() method returns an object of type DataInteraction.

The following table describes the available matchers.

Table 1. Espresso matchers
ViewMatcher Description

withText("SOMETEXT")

Searches for a view with the specified text, for example onView(withText("User")) searches for a view with the text "User".

withId()

Similar to withText but searches for the ID

Hamcrest Matchers

You can use Hamcrest matchers, e.g., containsString or instanceOf(). It is also possible to combine matchers with allOf(). If you want to exclude views, use allOf() together with not()). Example: onView(allOf(withId(R.id.button_login), not(withText("Logout "))));

4.3. Performing Actions

ViewInteraction and DataInteraction allow to specify an action for test via an object of type ViewAction via the perform method. The ViewActions class provides helper methods for the most common actions, like:

  • ViewActions.click

  • ViewActions.typeText()

  • ViewActions.pressKey()

  • ViewActions.clearText()

The perform method returns again an object of type ViewInteraction on which you can perform more actions or validate the result. It also uses varags as argument, i.e, you can pass several actions at the same time to it.

4.4. Verifying test results

Call the ViewInteraction.check() method to assert a view state. This method expects a ViewAssertion object as input. The ViewAssertions class provides helper methods for creating these objects:

  • matches - Hamcrest matcher

  • doesNotExist - asserts that the select view does not exist

You can use the powerful Hamcrest matchers. The following gives a few examples:

onView(withText(startsWith("ABC"))).perform(click()); (1)

onView(withText(endsWith("YYZZ"))).perform(click()); (2)

onView(withId(R.id.viewId)).check(matches(withContentDescription(containsString("YYZZ")))); (3)

onView(withText(equalToIgnoringCase("xxYY"))).perform(click()); (4)
 -
onView(withText(equalToIgnoringWhiteSpace("XX YY ZZ"))).perform(click()); (5)

onView(withId(R.id.viewId)).check(matches(withText(not(containsString("YYZZ"))))); (6)
1 Matches a view which text starts with "ABC" pattern
2 Matches a view which text ends with "YYZZ" pattern
3 Matches that the text of the view with specified R.id has content description which contains "YYZZ" string anywhere
4 Matches a view which text is equal to the specified string, ignoring case:
5 Matches a view which text is equal to the specified text when whitespace differences are (mostly) ignored
6 Matches that text of a particular view with specified R.id does not contain "YYZZ" string

4.5. Access to the instrumentation API

Via the InstrumentationRegistry.getTargetContext() you have access to the target context of your application. For example, if you want to use the id without using R.id you can use the following helper method to determine it.

package testing.android.vogella.com.asynctask;

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.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class EspressoTest {

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

    @Test
    public void buttonShouldUpdateText(){
        onView(withId(R.id.update)).perform(click());
        onView(withId(getResourceId("Click"))).check(matches(withText("Done")));
    }

    private static int getResourceId(String s) {
        Context targetContext = InstrumentationRegistry.getTargetContext();
        String packageName = targetContext.getPackageName();
        return targetContext.getResources().getIdentifier(s, "id", packageName);
    }
}

4.6. Configuring the start intent for the activity

If you specify false as third parameter in the ActivityTestRule, you can configure the intent for starting the activity. This is as demonstrated in the following code example.

package com.vogella.android.testing.espressosamples;

import android.content.Intent;
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.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class SecondActivityTest {

    @Rule
    // third parameter is set to false which means the activity is not started automatically
    public ActivityTestRule<SecondActivity> rule =
        new ActivityTestRule(SecondActivity.class, true, false);

    @Test
    public void demonstrateIntentPrep() {
        Intent intent = new Intent();
        intent.putExtra("EXTRA", "Test");
        rule.launchActivity(intent);
        onView(withId(R.id.display)).check(matches(withText("Test")));
    }
}

4.7. Adapter views

AdapterView is a special type of widget that loads its data dynamically from an adapter. Only a subset of the data has real views in the current view hierarchy. A onView() search would not find views for them. onData can be used to interactive with adapter views, like ListView. The following gives a few examples.

// click on an item of type String in a spinner
// afterwards verify that the view with the R.id.spinnertext_simple id contains "Eclipse"
onData(allOf(is(instanceOf(String.class)), is("Eclipse"))).perform(click());
onView(withId()).check(matches(withText(containsString("Eclipse")))); // normal view not adapter view

onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50"))).perform(click());

onData(withItemContent("item: 60")).onChildView(withId(R.id.item_size)).perform(click());

4.8. Espresso testing with permissions

Via instrumentation you can grand your tests the permission to execute.

@Before
public void grantPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm grant " + getTargetContext().getPackageName()
                        + " android.permission.CALL_PHONE");
    }
}

4.9. Espresso UI recorder

Android Studio provides an Run  Record Espresso Test menu entry which allows you to record the interaction with your application and create a Espresso test from it.

espresso test uirecorder10
espresso test uirecorder20

4.10. Configuring the activity under test

You can also access the activity object which you are testing and call methods on it. For example, assume you want to call a method on your activity.

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    public void configureMainActivity(String Uri) {
        // do something with this
    }
}

This configureMainActivity can be called in your test.

package com.vogella.android.myapplication;

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;

@RunWith(AndroidJUnit4.class)

public class ExampleInstrumentedTest {

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

    @Test
    public void useAppContext() throws Exception {
        MainActivity activity = mActivityRule.getActivity();
        activity.configureMainActivity("https://www.vogella.com/");
        // do more
    }
}

You can also override methods in ActivityTestRule, for exmaple the beforeActivityLaunched and afterActivityLaunched methods.

You can also access the current activity.

@Test
public void navigate() {

        Activity instance = getActivityInstance();
        onView(withText("Next")).perform(click());
        Activity activity = getActivityInstance();
        boolean b = (activity instanceof  SecondActivity);
        assertTrue(b);
        // do more
    }

    public Activity getActivityInstance() { (1)
        final Activity[] activity = new Activity[1];
        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable( ) {
            public void run() {
                Activity currentActivity = null;
                Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
                if (resumedActivities.iterator().hasNext()){
                    currentActivity = (Activity) resumedActivities.iterator().next();
                    activity[0] = currentActivity;
                }
            }
        });

        return activity[0];
    }
1 - Allows to access the currently active activity.

ActivityLifecycleMonitorRegistry is not API so this might change.

4.11. Running Espresso tests

4.11.1. Using Android Studio

Right-click on your test and select Run.

Run Espresso tests in Android Studio

4.11.2. Using Gradle

Use the connectedCheck task in Gradle to run the test directly via Gradle.

Running Gradle tasks via Android Studio

4.12. Checking for a toast

There is an example how you can click on an list item and check for a toast to be displayed.

package com.vogella.android.test.juntexamples;

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.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
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.withText;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

@RunWith(AndroidJUnit4.class)
public class MainActivityTestList {

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

    @Test
    public void ensureListViewIsPresent() throws Exception {
        onData(hasToString(containsString("Frodo"))).perform(click());
        onView(withText(startsWith("Clicked:"))).
        inRoot(withDecorView(
            not(is(rule.getActivity().
            getWindow().getDecorView())))).
            check(matches(isDisplayed()));
    }
}

5. Mocking intents with Espresso Intents

Espresso provides also the option to mock intents. This allows you to check if an activity has issued the correct intents and reacts correct if it receives the correct intent results.

Espressos intents is provided by the com.android.support.test.espresso:espresso-intents library.

If you want to use Espresso intent in your Espresso tests, use the IntentsTestRule instead of ActivityTestRule.

package testing.android.vogella.com.simpleactivity;


import android.support.test.espresso.intent.rule.IntentsTestRule;
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.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static android.support.test.espresso.matcher.ViewMatchers.withId;


@RunWith(AndroidJUnit4.class)
public class TestIntent {

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

    @Test
    public void triggerIntentTest() {
        onView(withId(R.id.button)).perform(click());
        intended(allOf(
                hasAction(Intent.ACTION_CALL),
                hasData(INTENT_DATA_PHONE_NUMBER),
                toPackage(PACKAGE_ANDROID_DIALER)));
    }

}

6. Using Espresso to test asynchronous code

Testing asynchronous without framework support is challenging. The typical approach before Espresso was to wait for a predefined time. Or to use an instance of the CountDownLatch class in your test code and signal from the asynchronous processing that the processing was done. Espresso makes this much easier as it monitors automatically the thread pool behind the AsynchronousTask. It also monitors the event queue of the user interfacevand continues only with its test once no task is running.

If you use different resources, like an IntentService you need to implement an IdlingResource. This implementation must monitor this resource and register this monitor with the Espresso framework.

package com.vogella.android.espressointentservice;

import android.app.ActivityManager;
import android.content.Context;
import android.support.test.espresso.IdlingResource;

import java.util.List;

public class IntentServiceIdlingResource implements IdlingResource {

    ResourceCallback resourceCallback;
    private Context context;

    public IntentServiceIdlingResource(Context context) {
        this.context = context;
    }

    @Override
    public String getName() {
        return IntentServiceIdlingResource.class.getName();
    }

    @Override
    public void registerIdleTransitionCallback(
            ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }

    @Override
    public boolean isIdleNow() {
        boolean idle = !isIntentServiceRunning();
        if (idle && resourceCallback != null) {
            resourceCallback.onTransitionToIdle();
        }
        return idle;
    }

    private boolean isIntentServiceRunning() {
        ActivityManager manager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        // Get all running services
        List<ActivityManager.RunningServiceInfo> runningServices =
                manager.getRunningServices(Integer.MAX_VALUE);
        // check if our is running
        for (ActivityManager.RunningServiceInfo info : runningServices) {
            if (MyIntentService.class.getName().equals(
                    info.service.getClassName())) {
                return true;
            }
        }
        return false;
    }
}
package com.vogella.android.espressointentservice;

import android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

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

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.notNullValue;

@RunWith(AndroidJUnit4.class)
public class IntegrationTest {

    @Rule
    public ActivityTestRule rule = new ActivityTestRule(MainActivity.class);
    IntentServiceIdlingResource idlingResource;

    @Before
    public void before() {
        Instrumentation instrumentation
                = InstrumentationRegistry.getInstrumentation();
        Context ctx = instrumentation.getTargetContext();
        idlingResource = new IntentServiceIdlingResource(ctx);
        Espresso.registerIdlingResources(idlingResource);

    }
    @After
    public void after() {
        Espresso.unregisterIdlingResources(idlingResource);

    }

    @Test
    public void runSequence() {
        // this triggers our intent service, as we registered
        // Espresso for it, Espresso wait for it to finish
        onView(withId(R.id.action_settings)).perform(click());
        onView(withText("Broadcast")).check(matches(notNullValue()));
    }
}

7. Exercise: Creating a custom Espresso matcher

Android provides the BoundedMatcher class which allows to create Espresso view matchers for specific view types.

Lets write a matcher which checks the text hints of an EditText field. See http://qathread.blogspot.de/2014/03/descovering-espresso-for-android.html

public static Matcher<View> withItemHint(String itemHintText) {
  checkArgument(!(itemHintText.equals(null)));
  return withItemHint(is(itemHintText));
}

public static Matcher<View> withItemHint(final Matcher<String> matcherText) {
  // use preconditions to fail fast when a test is creating an invalid matcher.
  checkNotNull(matcherText);
  return new BoundedMatcher<View, EditText>(EditText.class) {

    @Override
    public void describeTo(Description description) {
      description.appendText("with item hint: " + matcherText);
    }

    @Override
    protected boolean matchesSafely(EditText editTextField) {
      return matcherText.matches(editTextField.getHint().toString());
    }
  };
}

It can be used via the following code:

import static com.your.package.test.Matchers.withItemHint;
...
onView(withItemHint("test")).check(matches(isDisplayed()));

8. Exercise: Write a test for an intent with Espresso

8.1. Create project which is tested

Create a new Android project with the testing.android.vogella.com.simpleactivity package and the Empty Activity template.

Add a second activity called SecondActivity to your project via File  New  Activity  Empty Activity. This activity should use a layout with at least one TextView. The id of the TextView should be "resultText" and its text should be set to "Started".

Add an EditText field to the layout of the MainActivity class.

Add a button to the layout used by MainActivity. If this button is clicked, the second activity should be started.

Put the text EditText field as extra into the intent using "text" as key. Also put the "https://www.vogella.com/" String as extra into the intent using the key "URL".

Here is some example code for the MainActivity.

package testing.android.vogella.com.simpleactivity;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

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

    public void onClick(View view) {
        Intent intent = new Intent(this, SecondActivity.class);
        intent.putExtra("URL", "https://www.vogella.com/");
        startActivity(intent);
    }
}

8.2. Write tests

Write a Espresso test for the activity which tests the following:

  • Check that the layout of the MainActivity contains a button with the R.id.button ID

  • Ensure that the text on the button is "Start new activity"

  • Ensure that if the getActivity.onClick() method is called, that the correct intent is triggered. This intent should contain the String extra (hasExtra("URL", "https://www.vogella.com/").

8.3. Validate

Your test code should look similar to the following example code.

package testing.android.vogella.com.simpleactivity;


import android.support.test.espresso.intent.rule.IntentsTestRule;
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.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static android.support.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.IsNull.notNullValue;


@RunWith(AndroidJUnit4.class)
public class TestIntent {

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

    @Test
    public void triggerIntentTest() {
        // check that the button is there
        onView(withId(R.id.button)).check(matches(notNullValue() ));
        onView(withId(R.id.button)).check(matches(withText("Start new activity")));
        onView(withId(R.id.button)).perform(click());
        intended(toPackage("testing.android.vogella.com.simpleactivity"));
        intended(hasExtra("URL", "https://www.vogella.com/"));
    }

}

9. Exercise: functional test for activities

In this exercise, you start a second activity and validate that is has been started.

9.1. Write functional test for activities

package testing.android.vogella.com.simpleactivity;


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.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;


@RunWith(AndroidJUnit4.class)
public class TestSecondActivityIsStarted {

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

    @Test
    public void validateSecondActivity() {
        // check that the button is there
        onView(withId(R.id.button)).perform(click());
        onView(withId(R.id.resultText))
                .check(matches(withText(("Started"))));
    }

}

To test the direct modification of a view, create the following test class for the SecondActivity class.

package testing.android.vogella.com.simpleactivity;


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.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;


@RunWith(AndroidJUnit4.class)
public class SecondActivityFunctionalTest {

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

    @Test
    public void validateSecondActivity() {
        // check that the button is there
        onView(withId(R.id.button)).perform(click());
        onView(withId(R.id.resultText))
                .check(matches(withText(("Started"))));
        pressBack();
        onView(withId(R.id.button))
                .check(matches(withText(("Start new activity"))));

    }

}

10. Exercise: Testing asynchronous code with Espresso

Create an project with the package called testing.android.vogella.com.asynctask which allows to trigger an AsyncTask via a button.

The example code for the activity:

package testing.android.vogella.com.asynctask;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {


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

    public void onClick(View view) {
        TextView textView = (TextView) findViewById(R.id.text);
        textView.setText("Running");
        myTask.execute("test");
    }

    final AsyncTask<String, Void, String> myTask = new AsyncTask<String, Void, String>() {

        @Override
        protected String doInBackground(String... arg0) {
            return "Long running stuff";
        }

        @Override
        protected void onPostExecute(String result) {
            TextView textView = (TextView) findViewById(R.id.text);
            textView.setText("Done");
        }

    };

}

The example code for the test:

package testing.android.vogella.com.asynctask;

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.rules.TestName;
import org.junit.runner.RunWith;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
public class EspressoTest {

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

    @Test
    public void buttonShouldUpdateText(){
        onView(withId(R.id.update)).perform(click());
        onView(withId(R.id.text)).check(matches(withText("Done")));
    }

}