Lipika Gupta

Basics of Espresso: Introduction & Setup

Unit testing is an essential part of any development lifecycle. Writing unit test cases can come in handy when we want to make sure that our code still works as expected even after code changes have been implemented.

In Android, we have two types of unit tests that we can write:

  1. JUnit Test Cases
  2. Android Instrumentation Test Cases

JUnit test cases are test cases that can run on the JVM. It is preferred to write JUnit test cases as they can run directly on the JVM and are much faster. While writing local unit test cases, if there is any dependency on the Android system, those dependencies can be mocked using a mocking framework like Mockito.

Android Instrumentation test cases, on the other hand, need the Android system to run. They need an actual Android device to run on. They should be used if the test cases rely heavily on the Android system like the Android UI. As they first need to be deployed on an Android device before they can run, they are usually slower than local unit tests.

In this blog, I will talk about how we can write basic Espresso test cases to test the Android UI.

Let’s start with the basics of Espresso and how it can be integrated into our Android projects.

basics of Espresso for Android

Espresso has 4 main components which are:

  1. Espresso – It is the entry point for any interaction with views (using onView() and onData()). It also exposes certain APIs that are not specifically linked with any view, like pressBack().
  2. ViewMatchers – They are a collection of objects implementing the Matcher <? super View> interface. One or more matchers can be passed to the onView() method to find a view inside the current view’s hierarchy. In case the view that we are trying to match is not present in the view hierarchy, Espresso throws a NoMatchingViewException.
  3. ViewActions – They are a collection of actions (ViewAction) that can be passed as an argument to the ViewInteraction.perform() method to perform some action on the view, such as click().
  4. ViewAssertions – They are a collection of assertions (ViewAssertion) that can be passed as an argument to the ViewInteraction.check() method to assert some state of the view. Most commonly used ViewAssertion is the matches assertion, which uses a ViewMatcher to assert the state of the currently selected view.

A complete list of the available ViewMatcher, ViewAction, and ViewAssertion can be found in this Espresso Cheat Sheet.

Now that we know the basic components of Espresso, let’s see how we can integrate Espresso into our Android projects. Let’s start with setting up our test environment.

It is recommended to turn off system animations on the device on which the tests will be run. This should be done in order to avoid test flakiness. Go to Settings -> Developer Options and turn the following 3 animations off:

  1. Window animation scale
  2. Transition animation scale
  3. Animator duration scale

To add Espresso to our app, we need to add the following dependencies in the app-level build.gradle file (app/build.gradle):

compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    androidTestCompile('com.android.support.test:runner:0.3') {
        exclude group: 'com.android.support', module: 'support-annotations'
    }
    
    //For testing RecyclerView
    androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
        exclude group: 'com.android.support', module: 'appcompat'
        exclude group: 'com.android.support', module: 'support-v4'
        exclude group: 'com.android.support', module: 'support-annotations'
        exclude group: 'com.android.support', module: 'design'
        exclude module: 'recyclerview-v7'
    }

 

Also, we need to add the following in android.defaultConfig block in the app-level build.gradle file (app/build.gradle):

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

 

Android Studio creates tests in src/androidTest/java/com.example.package/ by default. Now, let’s see what a sample test would look like:

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

    @Before
    public void before(){
        //initial setup code
    }

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

    @Test
    public void checkButtonClick(){
        onView(withId(R.id.btn_go)).perform(click());
    }
    
    @After
    public void after(){
        //clean up code
    }
}

 

We have to first annotate our test class with the @RunWith(AndroidJUnit4.class) annotation. This indicates that JUnit will invoke the AndroidJUnit4 class to run this test class.

Let’s have a look at the ActivityTestRule lifecycle:

 

We can override beforeActivityLaunched() to execute any code that should run before our Activity is created and launched.

We can override afterActivityLaunched() to execute any code that should run after our Activity is created and launched but before any test case is executed.

We can override afterActivityFinished() to execute any code that should run after our Activity has finished.

Methods annotated with @Before annotation are executed after the activity is launched and before the test case is executed.

Methods annotated with @After annotation are executed after the test case is executed and before the activity finishes.

ActivityTestRule rule provides functional testing of a single activity, that which is specified in the ActivityTestRule. This activity will be launched before executing every test annotated with the @Test annotation. ActivityTestRule has 3 constructors:

public ActivityTestRule(Class<T> activityClass) {
    this(activityClass, false);
}

  
public ActivityTestRule(Class<T> activityClass, boolean initialTouchMode) {
    this(activityClass, initialTouchMode, true);
}

   
public ActivityTestRule(Class<T> activityClass, boolean initialTouchMode, boolean launchActivity) {
    ...
}

 

The activityClass parameter indicates the activity class that needs to be launched before every test.

The initialTouchMode parameter indicates whether the activity should be launched in touch mode.

The launchActivity parameter indicates whether the activity should be launched by default before every test case is executed.

Let’s see how we can pass some data to the activity before any test cases are executed. There are two ways in which this can be achieved.

First, we can use the 3rd ActivityTestRule constructor along with the @Before annotation to achieve this. Passing the launchActivity parameter as false indicates that the activity will not be launched by default. The method annotated with the @Before annotation will be executed before every test case and in the below example we launch the activity from this method.

@Before
public void setup(){
    Intent intent = new Intent();
    intent.putExtra("extra_id", 1);
    mActivityTestRule.launchActivity(intent);
}

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<>(MainActivity.class, true, false);

 

The second way is to implement the ActivityTestRule constructor and override the getActivityIntent method. The getActivityIntent method returns the custom intent that we build and this intent is then available to the activity under test after it’s launched.

@Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule<MainActivity>(NudgeActivity.class) {
    @Override
    protected Intent getActivityIntent() {
        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
        Intent intent = new Intent(context, MainActivity.class);
        intent.putExtra("extra_id", 1);
        return intent;
    }
};

 

We have seen how to setup Espresso and how simple test cases can be written. I hope this article motivates you to implement Espresso in your Android applications for UI testing.

I will explain how we can test different components in the second part of this blog.

I hope you enjoyed reading this blog.