Lesson Weekend

Next, let’s learn the basics of instrumentation testing in Android. We will use Espresso; a UI test framework that creates automated tests that run on an actual device or emulator. Instrumentation tests are meant to simulate the actions of a user and allow us to test the app through the stages of the android activity lifecycle.

Configuring Espresso

First, we’ll add the necessary configurations and dependencies to our build.gradle file:

build.gradle (Module: app)
apply plugin: 'com.android.application'

android {
    ...

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

dependencies {
    ...
    androidTestCompile('com.android.support.test.espresso:espresso-core: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'
    }
}

Creating Test Classes and Rules

Next, let’s create a new test class called MainActivityInstrumentationTest inside our java/com.epicodus.myrestaurants (androidTest) package. We'll place the following annotation within this file, and import all necessary classes.

MainActivityInstrumentationTest.java
package com.epicodus.myrestaurants;

import android.support.test.rule.ActivityTestRule;

import org.junit.Rule;

public class MainActivityInstrumentationTest {

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

}

The code in @Rule tells our device which activity to launch before each test. Here, we're instructing the instrumentation tests to launch the MainActivity before each test.

Writing Instrumentation Tests with Espresso

Now we are ready to write our first test. Include the @Test block detailed below. Multiple methods will appear red. This is normal, continue reading the next section to address importing these methods appropriately.

MainActivityInstrumentationTest.java
public class MainActivityInstrumentationTest {

    ...

    @Test
    public void validateEditText() {
        onView(withId(R.id.locationEditText)).perform(typeText("Portland"))
                .check(matches(withText("Portland")));
    }
}

Importing Static Methods

You will notice that multiple methods included in this block of code will be red. Each of these are static methods from Espresso that need to be imported, similar to the manner in which we import classes. Click on each, select Option + Enter, and select "import static method..." from the resulting menu.

Once complete, your file should have these imports and annotations:

MainActivityInstrumentationTest.java
package com.epicodus.myrestaurants;

import android.support.test.rule.ActivityTestRule;

import org.junit.Rule;
import org.junit.Test;

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

public class MainActivityInstrumentationTest {

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

    @Test
    public void validateEditText() {
        onView(withId(R.id.locationEditText)).perform(typeText("Portland"))
                .check(matches(withText("Portland")));
    }

}

Anatomy of an Espresso Instrumentation Test

Now that everything is imported correctly, let's look at this test a little closer. This test verifies that the instrumentation test can type the text "Portland" into our locationEditText, and the text "Portland" will be visible in the EditText.

  • onView() specifies that we want to interact with a view
  • withId() is a ViewMatcher method that allows us to find specific views by ID
  • typeText() is a ViewAction method that allows us to type the specified text into our EditText
  • matches() is a ViewAssertion method that validates the specific properties of the given view

Check out this handy cheat sheet to see the available Matchers, ViewActions and ViewAssertions.

Running Instrumentation Tests

Let’s run our test and make sure it passes. Right click on the class name and select Run ‘MainActivityInstrume…’. Because this is an instrumentation test, the emulator will open, the “Portland” text will appear in the locationEditText, and the progress indicator will notify us that our test passed. Hooray!

Other Instrumentation Tests

Let’s add one more test to check if the location entered into our form is successfully being passed to our RestaurantsActivity with the intent extra we just recently created.

The code for this test will appear as follows:

MainActivityInstrumentationTest.java
...
@Test
    public void locationIsSentToRestaurantsActivity() {
        String location = "Portland";
        onView(withId(R.id.locationEditText)).perform(typeText(location));
        onView(withId(R.id.findRestaurantsButton)).perform(click());
        onView(withId(R.id.locationTextView)).check(matches
            (withText("Here are all the restaurants near: " + location)));
    }
...

Here, we will need to follow the same process detailed above to import the static click() method for use.

For this test, the app will navigate to the RestaurantsActivity where it will display the same location string that was entered in the locationEditText.

If you run the MainActivityInstrumentationTest now, you will notice that the test we just wrote fails. Why is this? Instrumentation tests are designed to simulate how a user interacts with an app. When using an app, a user can only click on a view such as the findRestaurantsButton if it is visible and unobstructed by other views or widgets. Our test is failing because the soft input keyboard is covering the findRestaurantsButton when our test was instructed to click on it. To combat this, let's disable the soft input keyboard on our emulator:

Navigate to your emulator's settings, select Language & input ...

language_and_input

... select Current Keyboard ...

language_and_input

...and finally toggle to disable the keyboard:

language_and_input

Using the provided examples, the linked resources on the helpful resources page, and your research skills, continue to implement both local and instrumentation unit tests.

Additional Resources



Example GitHub Repo for MyRestaurants after these changes

Terminology


  • Instrumentation Testing: Testing for user interfaces that requires an actual emulator or device to simulate user interaction with the application.

  • Espresso: A framework designed to test user interfaces with instrumentation tests.

Tips


  • Instrumentation test classes should have names that reflect the class they are testing. (For instance, instrumentation tests that correspond to the MainActivity will reside in a class called MainActivityInstrumentationTest).

Additional Resources