In this codelab, you'll learn how to get started with testing for Android. We'll look at the testing integration in Android Studio, unit testing, hermetic testing and functional UI testing. Later we will also cover some more advanced topics from the Espresso testing framework.

What you'll learn

What you'll need

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would rate your experience with building Android apps?

Novice Intermediate Expert

You can either download all the sample code to your computer as a zip file or directly from github (see below).

Download Zip

We have created snapshots for you for each step. If you get stuck you can just jump ahead to the next checkpoint and continue where you left off.

Each step builds on each other. Begin with ‘step-1-5' and use the checkpoints if you want to start over for the next section.

You can also checkout each snapshot directly from github, we set them up as separate branches.

The first snapshot is in the branch ‘step-1-5':

$ git clone https://github.com/googlecodelabs/android-testing -b step-1-5

We will be getting back to the code in the next step, where we will import the project into Android Studio and run the app for the first time. For now, just download the sample code and save it to your computer.

Notes - a simple note taking app

In this codelab, you will be working with a simple note taking app called "Notes". Don't worry, we have prepared most of the code for you.

You can view notes, open them, add new ones or attach a photo. In the next few steps, we will hook up the final parts of the app, explore the ‘Model-View-Presenter' architecture and - most importantly - add some tests.

Mobile applications run on devices that vary greatly in screen size and resolution, processing power, available memory or connectivity. Manually testing your application across all these different device configurations and external factors can be sheer impossible - that's where automated testing can help.

The Android Testing Support library (ATSL) provides a great framework for testing your Android app. It has a JUnit 4-compatible test runner (AndroidJUnitRunner) and functional UI testing through Espresso and UI Automator. You can run tests for these APIs directly in the Android Studio IDE or from the command line, which makes it very easy to integrate them into your development flow.

We'll look at these in some more detail and explore how to write your own tests using these great tools!

There are many different ways to architect your Android app. Not all of them are testable and allow you to structure your code in a sane way, so that the app is easy to test.

The key idea to a testable architecture is to separate parts of the application, making them easier to maintain and test individually. One common UI pattern is the Model-View-Presenter architecture, or MVP for short.

The Model-View-Presenter pattern

In our app, we follow the MVP pattern, separating the internal data model, from a passive view through a presenter that handles the logic of our application.

Model

The model provides and stores the internal data.
In our note taking app, the notes are represented in the model. Each note has some data (a title, description and image) that is served through a data source and API endpoint.( Notes are stored in a NotesRepository that is backed by a NotesServiceApi.)

View

The view (not to be confused with the Android class View) handles the display of data (ie. the model). User actions are forwarded to the presenter.
In our app, the view is represented by Fragments, but basically could be any Android View. We have three Views in our app:

Presenter

The presenter sits between the model and view: it coordinates the UI with the data, ensuring they are in sync. Specifically, it updates the view and acts upon user events that are forwarded by the view. The presenter also retrieves data from the model, prepares it for display (by the view) and updates the model as necessary.
We have three presenters in our app:

Learn more

Let's see how MVP works in action and look at how the different parts of our app work together.

Open the project in Android Studio.

If you have downloaded the zip file, extract it first then open the folder named step-1-5 in Android Studio. (Start Android Studio, then select File > Open and select the step-1-5 directory. You may have to select the file settings.gradle.)

This is our first snapshot and contains our starting point for this codelab. (We also have snapshots for each subsequent step later in the codelab.)

If you have run the app already, you will have noticed that we are getting a blank screen with no notes or working buttons! We'll implement the logic later, but for now we will explore the structure of our app and the MVP pattern.

Notes app structure: Feature separation by packages

Instead of using a package by layer approach, we have structured the application by package per feature. This greatly improves readability and modularizes the app in a way that parts of it can be changed independently from each other. Each key feature of the app is in its own Java package. Let's take a quick look:

Package: com.example.android.testing.notes

.addnote

"Add note": Adding a new note

.data

Data: Storage of notes - this contains the model layer

.notedetail

"Note Detail": Showing details for a single note

.notes

"Notes": Showing a list of all stored notes

.statistics

The statistics screen

.util

Utility classes used in various parts of the app, e.g. for handling file access

Each feature package (shown in bold above) contains a Contract interface - this defines the interface for the View and Presenter callbacks of each feature.

This makes it very clear which classes belong to the same feature and how they are interconnected.

Explore the "Notes" Features: Showing a list of notes

The first function we are exploring is the "show all notes" feature which is shown when the app is first started. The code for this feature lives in the package com.example.android.testing.notes.notes

We'll be looking at these classes:

NotesActivity

The entry point into our app. Hosts the NotesFragment

NotesContract.View

Defines the View layer for this feature

NotesContract.UserActionsListener

Defines the interaction between the View and Presenter layers

NotesFragment

Concrete implementation of the View layer

NotesPresenter

The Presenter layer, implements the NotesContract.UserActionsListener to listen for user actions. It has a reference to the View to update it when the model changes

View Layer

The NotesFragment class is the View for this feature - it implements the View interface defined in the NotesContract.View:

NotesFragment.java

public class NotesFragment extends Fragment implements NotesContract.View {
    ...

Take a look at the NotesContract.View interface. These are all functions that we expose from the View layer and make available to the Presentation Layer:

NotesContract.java

interface View {

   void setProgressIndicator(boolean active);

   void showNotes(List<Note> notes);

   void showAddNote();

   void showNoteDetailUi(String noteId);
}

The View layer handles the user interface and these are the only functions that we expose to other layers.

Presenter layer

The NotesPresenter class is the Presenter layer for this feature - it implements the UserActionsListener interface defined in NotesContract.UserActionsListener:

NotesPresenter.java

public class NotesPresenter implements NotesContract.UserActionsListener {

Take a look at the NotesContract.UserActionListener interface. It describes the actions that can be started from the View - for example opening or adding a note. The view should not handle user interaction directly where it affects the model. Instead, it should forward interactions to the presenter.

NotesContract.java

interface UserActionsListener {

   void loadNotes(boolean forceUpdate);

   void addNewNote();

   void openNoteDetails(@NonNull Note requestedNote);
}

Connecting the View and Presenter layers

Let's see how the interaction between these two layers works in practice. We are not adding any new code in this step, we are only exploring how the application is structured.

Open the NotesFragment (app/src/main/java/.../notes/NotesFragment.java).

Now temporarily comment out the mActionsListener field. This will show us where (and how) we make calls to the Presenter:

NotesFragment.java

//    private NotesContract.UserActionsListener mActionsListener;

For example, we are making a call when a click on the floating action button occurs to trigger the "Add Note" UI through the Presenter (mActionsListener.addNewNote()):

// Set up floating action button
FloatingActionButton fab = ...

fab.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       mActionsListener.addNewNote();
   }
});

Here we are asking the Presenter to display a specific note when it is clicked:

NoteItemListener mItemListener = new NoteItemListener() {
   @Override
   public void onNoteClick(Note clickedNote) {
       mActionsListener.openNoteDetails(clickedNote);
   }
};

Now, add the mActionsListener back, we need it to run the app!

private NotesContract.UserActionsListener mActionsListener;

Creating the NotesPresenter

Let's take a look at how exactly we are creating the NotesPresenter object in onActivityCreated(...):

NotesFragment.java

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        setRetainInstance(true);

        mActionsListener = new NotesPresenter(Injection.provideNotesRepository(), this);

    }

We are using the Injection class to retrieve an instance of the notes repository. Using this class makes it easy to swap out the implementation during testing later on - we can simply inject a different service layer in our tests, perhaps one that only emulates parts of the functionality in a predefined way.

We are now ready to run the app!

Open the Build Variants window and choose the prodDebug variant for your app module. (The option to open the Build Variants screen is on the lower, left hand side in Android Studio.)

Connect your Android device (or start the emulator) and select ‘Run' -> ‘Run app' to start the application:

When prompted to choose a target device, select your device and check the box to use the same device for future launches, then press ok.

You should see an empty screen with a floating action button (that doesn't do anything yet) and a navigation drawer:

Continue with your existing Android Studio project for this section.

In this section we will get the project configured for testing, set up Android Studio to run our tests and explore how you can use unit tests to establish a contract between components.

We will take a test-driven approach, this means that we will start by writing some tests that describe the functionality before adding their implementation. These tests will also aid us in defining the "contract" for the presenter which we introduced in previous steps. It helps you understand the responsibilities of your objects and how they communicate with each other, without implementing any details just yet. First we will look at implementing the notes list, that shows a list of all saved notes when the app is first started. We will write some tests that define the interaction between the notes presenter and the notes repository.

Configure your project for Unit testing support

Before you start writing tests, let's go through a checklist and make sure our project is set up for local testing. Local tests are tests that run on your local machine, without needing access to the Android framework or an Android device. (Remember, we will first test the basic interaction needed between components.)

Switch to the "Project" Perspective

Tests are stored in the test and androidTest directories in your module's src folder. Good news - we have already set these up for you!

These folders are hidden in the default Android perspective, so you will need to switch to the Project perspective by using the drop down on the top-right of the project pane. (If you can't see it, open the Project view first, it's on the left - or you can enable it under View > Tool Windows > Project.)

Your final view hierarchy should now look like this: Note the new folders that have appeared in the src directory in our app module.

Source sets define product flavors

The directories in the src/ folder are called source sets that make up a product flavor. We have four different source sets defined that will be picked up by different parts of the build and test process. Our unit tests will go into the test/ directory.

A product flavor defines a customized version of the application built by the project. A single project can have different flavors which change the generated application.

This new concept is designed to help when some parts of the codebase need to be different for variations of the app. We're going to use two flavors for this project: mock and prod. We'll use the mock flavor to substitute certain parts of the application to make hermetic testing easier.

The majority of the code will still exist in the main directory and only the files that need some variation will have a version in mock/ and another in prod/. The same also applies to Android tests (most tests live in androidTest/, but those that make use of the special implementation in mock/ are under androidTestMock/).

Source Set (directory)

Explanation

androidTest

Location of Android tests (tests that are run on a device or emulator)

androidTestMock

Location for Android tests for the mock flavor of the application.
Contains tests that should only run against the mock flavor of the app. This is particularly useful if you want some tests to only run in an hermetic environment.

main

The main source set, default location for all source code

mock

Custom app flavor (mock) that is used for tests.
Within it, the class Injection provides dummy implementations of dependencies that use fake data for an isolated execution, not hitting the network or external storage (mock = mocked dependency that simulates the behavior of a complex, real object)

prod

Opposite to the mock flavor, here the class Injection provides dependencies for a real-world context . (prod = production implementation)

test

Location of local tests

Adding Gradle dependencies for JUnit 4

The first test we will be adding is a JUnit test. We have already added the necessary dependencies to our build.gradle file in the app module. We will also be using Mockito and Hamcrest matchers in this step - more about this later. For now, this is what our dependencies look like:

app/build.gradle

// Dependencies for local unit tests
testCompile "junit:junit:$rootProject.ext.junitVersion"
testCompile "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testCompile "org.hamcrest:hamcrest-all:$rootProject.ext.hamcrestVersion"
testCompile "org.powermock:powermock-module-junit4:$rootProject.ext.powerMockito"
testCompile "org.powermock:powermock-api-mockito:$rootProject.ext.powerMockito"

Optionally sync your project

Sometimes changing the test artifact can result in errors, synchronizing your project again with Android Studio will in most cases resolve these issues!

Click the gradle sync button in the toolbar. Any missing dependencies will now be downloaded.

You might need an Internet connection to download the JUnit dependency when syncing your Gradle configuration.

Create your first Unit Tests: Notes Presenter

Now that everything is set up, it's time to start writing your first test. A unit test exercises a small component of your application, usually a class or method. Remember, we will be writing our unit test first and implement the logic afterwards.

Let's implement a few tests for the NotesPresenter.
The notes presenter sits between the data model and the view. It toggles the state of the view (show a loading indicator, display a list of notes, display an empty indicator), based on callbacks received. It also handles the display of different views when options are selected on the screen, such as showing note details or the "add note" UIs.

Mocking dependencies with Mockito

The first tests we are writing focuses purely on the NotesPresenter. However, the NotesPresenter class does not stand in isolation - it also requires a view that updates the UI (NotesContract.View) and a repository where our notes are stored. We don't care about the concrete implementation of the View and repository at this point, we only want to make sure that the presenter behaves as expected.

Instead of relying on actual implementations of these dependencies, we can use mock objects to test our class under test in isolation. A mock object is a dummy class for which you can define the output and behavior of its method calls based on their input.

This is perfect for our test - we don't know how the View might display a progress indicator, but we do know that the presenter must call a method on the View to tell it to display the progress indicator. The tests we will be writing next will focus on this interaction - we test if the presenter has made the calls on the view that we expect it to make when we call its methods.

One popular framework for mocking on Android is Mockito. You'll see some of its syntax in the next section.

Another advantage of using a mocking framework like Mockito is the option to test against arguments of calls. This means that you can write tests that capture the parameters supplied to a method and test against them. (In Mockito this functionality is implemented through an ArgumentCaptor.)

Unit Test 1: Opening the ‘Add Notes' screen

Unit tests live in the test/ source set.

Open the file app/src/test/java/.../notes/NotesPresenterTest.java and comment in the code as you work through this section:

Our first test verifies that asking the Presenter to ‘Add a new note' results in a request to be made for the View to show the new add-note screen. Let's implement the clickOnFab_ShowsAddsNoteUi()test.
First, remove the fail(...) line, because we are now implementing this test!
Next, add a call to the presenter's addNewNote() method, after which we verify that the showAddNote() method has been called on the View.

This is our final implementation:

test/.../NotesPresenterTest.java

@Test
public void clickOnFab_ShowsAddsNoteUi() {
   // When adding a new note
   mNotesPresenter.addNewNote();

   // Then add note UI is shown
   verify(mNotesView).showAddNote();
}

Running your Unit Tests

Now it's finally time to run your test!

Right click on the NotesPresenterTest class we have just worked on and select Run > NotesPresenterTest.

You can also run the test from the command line, by issuing the following command in your project's directory:

./gradlew testMockDebugUnitTest --tests *NotesPresenterTest

Congratulations! You have just written your first unit test! (The test should fail for now.)

(You may have to switch to the "Run" view to see the status of your Unit Test runs.)

For now the test is failing, because we haven't implemented the relevant logic in the presenter yet.

Implement NotesPresenter#addNewNote

Open the file app/src/main/java/.../notes/NotesPresenter.java and implement the method addNewNote().

Based on our Unit test we expect this method to call the View's showAddNote() to trigger the UI update.

This will be our implementation:

NotesPresenter.java

@Override
public void addNewNote() {
   mNotesView.showAddNote();
}

Run the test again - it should now complete successfully! (Right click on NotesPresenterTest and select Run > NotesPresenterTest)

The test will no longer be included in the "Run" View, because it only displays tests that have failed.

You can also toggle view to display all passed tests using the button :

Bonus - Unit Test 2: Loading notes from presenter

Our next test validates that the presenter correctly displays a list of notes - this includes retrieving the notes from the repository and updating the view (displaying a progress indicator and finally displaying the notes).

The flow we will be testing is this:

Let's go back to the file app/src/test/java/.../notes/NotesPresenterTest.java and comment in the lines in this step as you follow along.

We are implementing the test loadNotesFromRepositoryAndLoadIntoView. Remove the call to fail(...) first:

test/.../NotesPresenterTest.java

@Test
public void loadNotesFromRepositoryAndLoadIntoView() {
    ...
}

First we are telling the NotesPresenter to load notes:

// Given an initialized NotesPresenter with empty notes
// When loading of Notes is requested
mNotesPresenter.loadNotes(true);

Next we will use Mockito's verify(...) to check that the getNotes(...) method has been called by the presenter, while capturing the parameter supplied to the call. The parameter is actually a callback that would be called by the repository when the data has been loaded (See NotesRepository.LoadNotesCallback)

Our repository here is only a mock object (and doesn't have any actual functionality), we need to fake the callback on the listener ourselves. (The callback would have been set up by the presenter - which is what we are trying to test here.)

First we capture the callback in the captor, then we call it with a list of notes:

// Callback is captured and invoked with stubbed notes
verify(mNotesRepository).getNotes(mLoadNotesCallbackCaptor.capture());
mLoadNotesCallbackCaptor.getValue().onNotesLoaded(NOTES);

In the last step we verify that the presenter has asked the view to hide the progress indicator and display the list of notes:

// Then progress indicator is hidden and notes are shown in UI
verify(mNotesView).setProgressIndicator(false);
verify(mNotesView).showNotes(NOTES);

Our final test should now look like this:

test/.../NotesPresenterTest.java

@Test
public void loadNotesFromRepositoryAndLoadIntoView() {
   // Given an initialized NotesPresenter with initialized notes
   // When loading of Notes is requested
   mNotesPresenter.loadNotes(true);

   // Callback is captured and invoked with stubbed notes
   verify(mNotesRepository).getNotes(mLoadNotesCallbackCaptor.capture());
   mLoadNotesCallbackCaptor.getValue().onNotesLoaded(NOTES);

   // Then progress indicator is hidden and notes are shown in UI
   verify(mNotesView).setProgressIndicator(false);
   verify(mNotesView).showNotes(NOTES);
}

Run the test. (Right click on NotesPresenterTest and select Run > NotesPresenterTest).

The test will fail, but we have a helpful error message that tells us exactly what we need to do:

Wanted but not invoked:
mNotesRepository.getNotes(
    <Capturing argument>
);

This means that we expected mNotesRepository.getNotes(...)to be called, but the call was never made by the NotesPresenter!

We haven't implemented the logic in the NotesPresenter yet - let's do that next to get our test to pass.

Implement NotesPresenter#loadNotes

Open the file app/src/main/java/.../notes/NotesPresenter.java and comment in the lines as you work through this step.

Go to the method loadNotes:

NotesPresenter.java

@Override
public void loadNotes(boolean forceUpdate) {
    ...
}

Remember the flow we need to implement:

This is our final implementation:

NotesPresenter.java

@Override
public void loadNotes(boolean forceUpdate) {
   mNotesView.setProgressIndicator(true);
   if (forceUpdate) {
       mNotesRepository.refreshData();
   }

   // The network request might be handled in a different thread so make sure Espresso knows
   // that the app is busy until the response is handled.
   EspressoIdlingResource.increment(); // App is busy until further notice

   mNotesRepository.getNotes(new NotesRepository.LoadNotesCallback() {
       @Override
       public void onNotesLoaded(List<Note> notes) {
           EspressoIdlingResource.decrement(); // Set app as idle.
           mNotesView.setProgressIndicator(false);
           mNotesView.showNotes(notes);
       }
   });
}

Run your test again - it should now pass.

(Right click on NotesPresenterTest and select Run > NotesPresenterTest).

Test driven development and contracts

The calls we have made from the presenter to the view are effectively a contract. The view must implement these calls to handle calls from the presenter. At this point we haven't looked at the UI yet, we only know how it should behave based on the logic we have implemented (through tests) in the presenter.

This kind of approach to development can be very useful to establish a contract between different components of your app, without getting distracted by platform implementation details.

In this codelab we have already defined these contracts (for example NotesContract.View that we have used in our tests).

Unit testing with Android dependencies

Explore another Unit test: NoteDetailPresenterTest

Open the file app/src/test/java/.../notes/notedetail/NotesDetailPresenterTest.java. It defines another set of Unit tests for the NotesDetailPresenter class that handles the display of a single note on screen.

We'll be implementing its matching UI test at the end of the next section, so take a look at the callbacks the View and Presenters expect:

test/.../NotesDetailPresenterTest.java

@Test
public void getNoteFromRepositoryAndLoadIntoView() {
   ...
   verify(mNoteDetailView).setProgressIndicator(true);
    ...
   verify(mNoteDetailView).setProgressIndicator(false);
   verify(mNoteDetailView).showTitle(TITLE_TEST);
   verify(mNoteDetailView).showDescription(DESCRIPTION_TEST);
}

Congratulations! You have just learned how to write unit tests for your Android app! Unit tests are a great way to ensure that the individual components of your app are correct. But what about testing the interaction between those units? This is where integration tests come into play, which we will cover in the next step.

Continue with your existing Android Studio project for this section.

Next, let's implement the user interface. We'll be following a similar approach as before. First we'll define the tests that verify how we expect the UI to behave, then the implementation follows from there.

UI Testing on Android with Espresso

The Android Testing Support Library contains the Espresso testing framework that provides APIs to simulate user interactions. Espresso has a very nice fluent, concise and readable Api to write functional UI tests and was built to make UI testing as frictionless as possible. This means that you can focus on writing tests without having to deal with unreliable and flaky tests.

Although there is support for running instrumentation tests in the Android framework, current development efforts are focused around the new AndroidJUnitRunner which is released as part of the Android Testing Support Library.

Set up Espresso and AndroidJUnitRunner

We have already set up the build dependencies for you, so there is no need to update our build configuration.
If you are adding Espresso to your own project you need to add the AndroidJUnitRunner and Espresso framework from the Android Testing Support Library to your build.gradle:

app/build.gradle

// Android Testing Support Library's runner and rules
androidTestCompile "com.android.support.test:runner:$rootProject.ext.runnerVersion"
androidTestCompile "com.android.support.test:rules:$rootProject.ext.rulesVersion"

// Espresso UI Testing dependencies.
androidTestCompile "com.android.support.test.espresso:espresso-core:$rootProject.ext.espressoVersion"
androidTestCompile "com.android.support.test.espresso:espresso-contrib:$rootProject.ext.espressoVersion"

Set up Android Studio for Android instrumentation tests

Unlike local unit tests, instrumentation tests must be placed in the androidTest source set (app/src/androidTest/java/...). Don't worry - we have already created the directory and files up for you.

Just to recap our project structure, we have three build variants for our app:

mockDebug

Uses the mock source set to inject a mock data repository where notes are stored

prodDebug

Debug build that uses the prod source set to inject the ‘real' production data repository

prodRelease

Signed Release build that uses a ‘real' data repository

The first test we are writing is using the prodDebug variant - make sure to select it for our test artifact before continuing. (Different tests and directories are activated based on the variant you have selected!)

Set up your device for Espresso tests

For this section of the codelab you will need a physical device or Android emulator, because we will be implementing the user interface.

Espresso Test 1: Opening the ‘Add Note' screen

Espresso can also perform user interactions (ViewActions) on a view, for example clicks and touches.

One of the key interactions in the app is clicking the floating action button to open the "add note" screen.

We will implement a test that opens the app, clicks that button and verifies that the correct screen is being displayed.

Edit the file app/src/androidTest/java/.../notes/NotesScreenTest.java and comment in the method clickAddNoteButton_opensAddNoteUi() as you work through this section:

First, we need to find the floating action button in the view hierarchy (which we can identify by its ID R.id.fab_notes) and click it. Then we check that the "add note" screen has been shown, we can easily do this by checking that the title of the screen (identified by its ID R.id.add_note_title) is being displayed:

androidTest/.../NotesScreenTest.java

@Test
public void clickAddNoteButton_opensAddNoteUi() throws Exception {
   // Click on the add note button
   onView(withId(R.id.fab_add_notes)).perform(click());

   // Check if the add note screen is displayed
   onView(withId(R.id.add_note_title)).check(matches(isDisplayed()));
}

Note that we do not have to wait for the add note screen to be displayed. This is the power of Espresso - it automatically waits for the UI thread to be idle before running any further check. There is no need to implement these waits and delays yourself.

Running Espresso tests

Now that we have written our first test, it's time to run it!

Right click on the class that where we have just implemented our test (androidTest/java/.../notes/NotesScreenTest.java) then select "Run NotesScreenTest ...". If you are not seeing this option, make sure you have switched to the Android Instrumentation Tests Build Variant (see above).

This approach works well if you are working on just a single test class, but doesn't scale if you want to run many different tests at the same time.

Let's set up a build configuration that will run all of our unit tests together

Fortunately, Android Studio makes this very easy! Just right click on the package name under androidTest/java/com.example.android.testing.notes and select "Create ‘Tests in com.exampl...'".

In the next dialog we can configure our new build configuration (such as excluding tests or selecting a target device), but for now the default options are just fine.

Select our new run configuration from the run configuration menu, then select run:

Keep an eye on your device - you can see Espresso execute our tests!

We will have a few failing tests for now - that's what we will be implementing in the rest of the codelab!

Their status is displayed in the Run Panel that opened at the bottom of the screen:

ActivityTestRule: Functional testing for Android Activities

The Android Testing Support Library includes two JUnit Rules that make it easy to write functional tests for Android Activities and Services.

JUnit test rules allow the alteration of test methods. It is run before any test is executed and can be shared between projects and classes.

The ActivityTestRule is a rule that provides functional testing of a single Activity. The annotated Activity will be launched before each annotated @Test and before any annotated @Before methods. The Activity is automatically terminated after the test is completed and all @After methods are finished.

View Matching and Assertions with Espresso

Espresso tests are written based on what a user might do while interacting with your app. The key concepts are locating and interacting with UI elements. The first step is to find a View you are interested in, then check its state or interact with it.

ViewMatchers select Views in the current view hierarchy. The most common ones are withId(...) (that finds Views with a specific ID) and withText(...) (that finds Views with a specific text), but there are many others, including matchers for state (selected, focused, enabled), content description and hierarchy (root and children), among others.

ViewActions are actions that can be performed on a View (for example click).

ViewAssertions are passed to the check(...) ViewAction to verify its state.

Espresso Test 2: Adding a note

Continue editing the file app/src/androidTest/java/.../notes/NotesScreenTest.java and comment in the code as you work through this section:

Let's go ahead and implement our first Espresso test from scratch: addNoteToNotesList().

This test will open the ‘Add Note' screen, enter some text, click the save button and verify that the text has been displayed.

First, make sure that the test has been set up, remove the fail(...) call and add some test data we will be checking:

androidTest/.../NotesScreenTest.java

@Test
public void addNoteToNotesList() throws Exception {
    String newNoteTitle = "Espresso";
    String newNoteDescription = "UI testing for Android";
    ...
}

Similar to the previous Espresso test we looked at, we will first find the floating action button (R.id.fab_add_notes) and click it:

// Click on the add note button
onView(withId(R.id.fab_add_notes)).perform(click());

Next (once the "add note screen" is open) we will enter our note text and save the note. (We don't need to wait for the new screen to open - Espresso will do this automatically for us. It waits until a View with the id R.id.fab_add_notes can be found.)

// Add note title and description
onView(withId(R.id.add_note_title)).perform(typeText(newNoteTitle), closeSoftKeyboard()); // Type new note title
onView(withId(R.id.add_note_description)).perform(typeText(newNoteDescription),
       closeSoftKeyboard()); // Type new note description and close the keyboard

// Save the note
onView(withId(R.id.fab_add_notes)).perform(click());

Next we are back on the "Notes list screen". We will ask Espresso to scroll the RecyclerView until it can find a note in the list that contains our description and verify that it is in fact displayed on the screen:

// Scroll notes list to added note, by finding its description
onView(withId(R.id.notes_list)).perform(
       scrollTo(hasDescendant(withText(newNoteDescription))));

// Verify note is displayed on screen
onView(withItemText(newNoteDescription)).check(matches(isDisplayed()));

This is our final implementation of the addNoteToNotesList() test:

androidTest/.../NotesScreenTest.java

@Test
public void addNoteToNotesList() throws Exception {
   String newNoteTitle = "Espresso";
   String newNoteDescription = "UI testing for Android";

   // Click on the add note button
   onView(withId(R.id.fab_add_notes)).perform(click());

   // Add note title and description
   // Type new note title
   onView(withId(R.id.add_note_title)).perform(typeText(newNoteTitle), closeSoftKeyboard());
   onView(withId(R.id.add_note_description)).perform(typeText(newNoteDescription),
           closeSoftKeyboard()); // Type new note description and close the keyboard

   // Save the note
   onView(withId(R.id.fab_add_notes)).perform(click());

   // Scroll notes list to added note, by finding its description
   onView(withId(R.id.notes_list)).perform(
           scrollTo(hasDescendant(withText(newNoteDescription))));

   // Verify note is displayed on screen
   onView(withItemText(newNoteDescription)).check(matches(isDisplayed()));
}

Run all Espresso tests again.

Our test NotesScreenTest#addNoteToNotesList() is now no longer listed as failing:

You can also toggle view to display all passed tests using the button .

Espresso test 3: Clicking a note

In the previous step we implemented a test for the notes Presenter that verified that calling the ‘open a note' call told the View to display note details. Now we will implement an end-to-end UI test that verifies that clicking a note will display the correct note on screen.

We will start again by implementing the test and then implementing the UI in the View.

Switch build variant to mockDebug

The test we are implementing now uses the mock source set to simulate a notes repository with a fake implementation. You need to switch your build variant to mockDebug to activate the correct tests. Our tests for the mockDebug variant live in app/src/andoidTestMock/....

Launch NoteDetailActivity with an Intent extra parameter

Edit the file app/src/androidTestMock/java/.../notedetail/NoteDetailScreenTest.java and comment in the method as you work through this section:

Our test is for the NoteDetailActivity, we have already defined an ActivityTestRule for that Activity. The additional parameters specify the touch mode and if the Activity should be automatically launched before each @Test. We'll need to supply an extra parameter to the Activity via a custom Intent, so we will handle the launch ourselves.

androidTestMock/.../NoteDetailScreenTest.java

@Rule
public ActivityTestRule<NoteDetailActivity> mNoteDetailActivityTestRule =
        new ActivityTestRule<>(NoteDetailActivity.class, true /* Initial touch mode  */,
                false /* Lazily launch activity */);

Next, we will set up a method annotated with @Before that accesses this rule to create the intent (with a supplied note ID that we wish to display) that will start the Activity. The ActivityTestRule handles the creation and the lifecycle of the Activity during the test. This set up is essentially similar to the call to NoteDetailActivity.newInstance(...) with a Note ID.

We are using the FakeNotesServiceApiImpl to store our notes and we have added one single note to our repository. Comment in the set up steps in intentWithStubbedNoteId():

androidTestMock/.../NoteDetailScreenTest.java

@Before
public void intentWithStubbedNoteId() {
   // Add a note stub to the fake service api layer.
   FakeNotesServiceApiImpl.addNotes(NOTE);

   // Lazily start the Activity from the ActivityTestRule this time to inject the start Intent
   Intent startIntent = new Intent();
   startIntent.putExtra(NoteDetailActivity.EXTRA_NOTE_ID, NOTE.getId());
   mNoteDetailActivityTestRule.launchActivity(startIntent);

   registerIdlingResource();
}

Implement the test NoteDetailScreenTest#noteDetails_DisplayedInUI

Implement the test noteDetails_DisplayedInUi() that verifies that the title, description and image match what we have just added. (Remember, the @Before method starts the NoteDetailActivity to the note we have just added.)

androidTestMock/.../NoteDetailScreenTest.java

@Test
public void noteDetails_DisplayedInUi() throws Exception {
   // Check that the note title, description and image are displayed
   onView(withId(R.id.note_detail_title)).check(matches(withText(NOTE_TITLE)));
   onView(withId(R.id.note_detail_description)).check(matches(withText(NOTE_DESCRIPTION)));
   onView(withId(R.id.note_detail_image)).check(matches(allOf(
           hasDrawable(),
           isDisplayed())));
}

Run the UI Test and Implement the logic

Next, run this test! Right click on the class NoteDetailsScreenTest and select Run > Run "NoteDetailsScreenTest" or use the build configuration we have set up before.

The test will fail because we haven't implemented the logic in our View yet.

Switch to the Run view in Android Studio to see which test failed (and why it failed).

The test will fail with an error similar to this:

android.support.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'with text: is "ATSL"' doesn't match the selected view.
Expected: with text: is "ATSL"
Got: "AppCompatTextView{id=2131492979, res-name=note_detail_title, visibility=VISIBLE, width=0, height=103, has-focus=false, has-focusable=fals
<... stacktrace ...>

This error tells us that the title TextView does not contain the expected text we have set on it.

We haven't implemented the hooks for the NoteDetailContract.View yet which handles the display of note details.

Edit the file app/src/main/java/.../noteDetail/NoteDetailFragment.java to add the missing logic:

Implement the methods: setProgressIndicator, hideDescription, hideTitle, showDescription and showTitle:

NoteDetailFragment.java

@Override
public void setProgressIndicator(boolean active) {
   if (active) {
       mDetailTitle.setText("");
       mDetailDescription.setText(getString(R.string.loading));
   }
}

@Override
public void hideDescription() {
   mDetailDescription.setVisibility(View.GONE);
}
@Override
public void hideTitle() {
   mDetailTitle.setVisibility(View.GONE);
}

@Override
public void showDescription(String description) {
   mDetailDescription.setVisibility(View.VISIBLE);
   mDetailDescription.setText(description);
}

@Override
public void showTitle(String title) {
   mDetailTitle.setVisibility(View.VISIBLE);
   mDetailTitle.setText(title);
}

Run the test again (NoteDetailScreenTest) - the test noteDetails_DisplayedInUi now passes:

Troubleshooting

A common error while running your tests in this codelab setup is:

If you run into this error uninstall all artifacts from your test device using Android studio. Open the Gradle view on the right hand side of Android studio. Open :app > Tasks > install and run the uninstallAll task by double clicking it.

Continue with your existing Android Studio project for this section.

Espresso-Intents is an extension to Espresso that focuses on the validation and mocking of Intents. It has a similar API to Mockito (that we have explored earlier for mocking Java code), but for stubbing Intents.

If your app takes advantage of the full power of the Android platform by intenting out to other applications or the platform, you can focus on your own functionality and rely on the platform and other apps to function as expected. With Espresso-Intents you can validate your Intents or even stub out Intents by responding with your own results.

Intent Matching

Espresso-Intents adds the functionality to intercept Intents based on some matching criteria. These matchers are defined via Hamcrest Matchers (that we briefly explored earlier in the mocking section), but usually the basic built-in matchers provide sufficient functionality: you can match based on action, data, extras and categories or any combination thereof.

Intent Stubbing

Espresso-Intents also adds functionality to provide stub implementations to matching intents, by intercepting Intents and providing a response for Activities that are started with startActivityForResult(...)

Clicking on the "Add image" button on the "Add Note" screen sends an Intent to the camera application on the device to take a photo. The image is then returned back to the app.

We will write a test that verifies that after a photo is taken from that screen, its thumbnail is displayed in our app.

Switch to the mockDebug Build Variant

Make sure to select the the mockDebug build variant:

Implement AddNoteScreenTest#addImageToNote_ShowsThumbnailInUi with Espresso-Intents

Edit the file app/src/androidTestMock/java/.../addnote/AddNoteScreenTest.java and comment in the following lines as you work through this section:

First we have already set up an IntentsTestRule. It is an extension of ActivityTestRule that we have used earlier and specifically built for using the Espresso-Intents API in functional UI tests. It initialises Espresso-Intents before each @Test and releases it after each run.

androidTestMock/.../AddNoteScreenTest.java

@Rule
public IntentsTestRule<NotesActivity> mNotesIntentsTestRule = new IntentsTestRule<>(NotesActivity.class);

Next, we will implement the test addImageToNote_ShowsThumbnailUi().
Remove the call to fail(...).

@Test
public void addImageToNote_ShowsThumbnailInUi() {
    ...
}

We will stub out all Intents that match the MediaStore.ACTION_IMAGE_CAPTURE action and always respond with a valid (RESULT_OK) ActivityResult (provided by a call to createImageCaptureActivityResultStub() ).

First we will create the result object:

ActivityResult result = createImageCaptureActivityResultStub();

Next we instruct Espresso to respond to all intents that have the MediaStore.ACTION_IMAGE_CAPTURE action with the result object we have just created:

intending(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)).respondWith(result);

Next we verify that no thumbnail image is being shown as a sanity check - no image has been taken yet.

// Check thumbnail view is not displayed
        onView(withId(R.id.add_note_image_thumbnail)).check(matches(not(isDisplayed())));

selectTakeImageFromMenu();

We select the "Add image" option in the menu using a short helper method:

selectTakeImageFromMenu();

selectTakeImageFromMenu() clicks the "Add image" button that fires off a MediaStore.ACTION_IMAGE_CAPTURE Intent. (If you are curious, we are using a convenience method called openActionBarOverflowOrOptionsMenu(...) from Espresso to open the menu.)

If the Intent was set up correctly, it will be captured by our test and a valid result will be returned. If the Intent does not match our expected value, it will not be matched and an assertion exception would be thrown.

Finally we check that the thumbnail is now being displayed:

onView(withId(R.id.add_note_image_thumbnail))
.check(matches(allOf(
    hasDrawable(),
    isDisplayed())));

This is our final addImageToNote_ShowsThumbnailInUi() test:

androidTestMock/.../AddNoteScreenTest.java

@Test
public void addImageToNote_ShowsThumbnailInUi() {
   // Create an Activity Result which can be used to stub the camera Intent
   ActivityResult result = createImageCaptureActivityResultStub();
   // If there is an Intent with ACTION_IMAGE_CAPTURE, intercept the Intent and respond with
   // a stubbed result.
   intending(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)).respondWith(result);

   // Check thumbnail view is not displayed
   onView(withId(R.id.add_note_image_thumbnail)).check(matches(not(isDisplayed())));

   selectTakeImageFromMenu();

   // Check that the stubbed thumbnail is displayed in the UI
   onView(withId(R.id.add_note_image_thumbnail))
           .perform(scrollTo()) // Scroll to thumbnail
           .check(matches(allOf(
                   hasDrawable(), // Check ImageView has a drawable set with a custom matcher
                   isDisplayed())));
}

Run the test!

It should pass:

By using the full power of the Android platform (and being a good app citizen), you can write tests that focus on your own functionality, without having to worry about functionality of the platform.

Continue with your existing Android Studio project for this section.

Espresso-contrib is an extension to the Espresso framework that contains a few additional, useful features that will help you write great tests with Espresso. Part of this component is support for RecyclerView that enables you to write ViewMatchers for items or perform specific RecyclerViewActions (such as scrolling to a specific item). It also contains explicit support for the navigation drawer through DrawerActions and DrawerMatchers.

Implement Espresso tests for the Navigation Drawer

Let's use some of these features of Espresso-contrib to write a test that verifies that opening the navigation drawer and selecting the ‘Statistics' option opens that screen and hides the drawer when selected.

Switch to the prodDebug build variant

This test is using the main implementation and does not rely on the mock target. Switch the build variant to prodDebug:

Implement AppNavigationTest#clickOnAndroidHomeIcon_OpensNavigation

Edit the file app/src/androidTest/java/.../notes/AppNavigationTest.java and comment in the following lines as you work through this section:

Let's add a new test called AppNavigationTest.

Make sure it is setup to run with the JUnit4 runner and is defined as a large test:

androidTest/.../AppNavigationTest.java

@RunWith(AndroidJUnit4.class)
@LargeTest
public class AppNavigationTest {
...
}

Espresso-Intents Test 1: Clicking "Navigate up" opens the Navigation Drawer

Let's write a small test that verifies that clicking the ‘Home' icon on the start screen opens the navigation drawer.

Implement AppNavigationTest#clickOnAndroidHomeIcon_OpensNavigation(), which:

androidTest/.../AppNavigationTest.java

@Test
public void clickOnAndroidHomeIcon_OpensNavigation() {
   // Check that left drawer is closed at startup
   onView(withId(R.id.drawer_layout))
           .check(matches(isClosed(Gravity.LEFT))); // Left Drawer should be closed.

   // Open Drawer
   onView(withContentDescription("Navigate up")).perform(click());

   // Check if drawer is open
   onView(withId(R.id.drawer_layout))
           .check(matches(isOpen(Gravity.LEFT))); // Left drawer is open open.
}

Run the test - clickOnAndroidHomeIcon_OpensNavigation should complete successfully:

Espresso-Intents Test 2: Clicking the statistics option in navigation drawer opens the statistics screen

Next, implement our test called clickOnStatisticsNavigationItem_ShowsStatisticsScreen() in the file using the drawer support from Espresso-contrib.

Edit the file app/src/androidTest/java/.../notes/AppNavigationTest.java and comment in the following lines as you work through this section:

We will be using a custom ViewAction that we have implemented for you: navigateTo(...). It provides an easy interface to match MenuItems in a NavigationView. Here we will be using it for the navigation drawer to find a specific item that we want to select. You can find its implementation in app/src/androidTest/.../action/NavigationViewActions.java if you want to find out how it works exactly.

androidTest/.../AppNavigationTest.java

@Test
public void clickOnStatisticsNavigationItem_ShowsStatisticsScreen() {
   // Open Drawer to click on navigation.
   onView(withId(R.id.drawer_layout))
           .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
           .perform(open()); // Open Drawer

   // Start statistics screen.
   onView(withId(R.id.nav_view))
           .perform(navigateTo(R.id.statistics_navigation_menu_item));

   // Check that statistics Activity was opened.
   String expectedNoStatisticsText = InstrumentationRegistry.getTargetContext()
           .getString(R.string.no_statistics_available);
   onView(withId(R.id.no_statistics)).check(matches(withText(expectedNoStatisticsText)));
}

One of the key parts of Espresso is its ability to synchronize all test actions. Espresso waits until the UI is idle before it moves to the next operation. Likewise, it waits for AsyncTask background operations to complete. In general, this should address the majority of test synchronizations in your application. If you have written UI tests before, you will appreciate this feature - there's no need to add waits or synchronization points to your app!

However, sometimes it is not possible to rely on automatic synchronisation, for instance when your app does background operations via non-standard means (managing threads directly or using custom Services). If you have run into a situation where you cannot rely on Espresso to automatically handle the synchronization for you, you can use idling resources and still rely on Espresso for synchronization.

As you use a resource that needs to be synchronized, register it with Espresso using Espresso.registerIdlingResource in your test set up (usually in your @Before method), and implement the IdlingResource interface to track their use.

Idling Resources in Practice

In our app we have wrapped the calls to SimpleCountingIdlingResource (which is a simple implementation of the IdlingResource interface) in the class EspressoIdlingResource that is used across the entire application. This makes it easy to use the idling resources across the entire app.

You can see it in action in the NotesPresenter, where we increment the counter just before we make an asynchronous call to access the notes api to load in a list of notes. This signals to Espresso that the app is busy and holds all further execution of Espresso commands until the app is marked as idle again after the callback returns:

NotesPresenter.java

@Override
public void loadNotes(boolean forceUpdate) {
    mNotesView.setProgressIndicator(true);
    if (forceUpdate) {
        mNotesRepository.refreshData();
    }

    // The network request might be handled in a different thread so make sure Espresso knows
    // that the app is busy until the response is handled.
    EspressoIdlingResource.increment(); // App is busy until further notice

    mNotesRepository.getNotes(new NotesRepository.LoadNotesCallback() {
        @Override
        public void onNotesLoaded(List<Note> notes) {
            EspressoIdlingResource.decrement(); // Set app as idle.
            mNotesView.setProgressIndicator(false);
            if (notes.isEmpty()) {
                mNotesView.showNotesEmptyPlaceholder();
            } else {
                mNotesView.showNotes(notes);
            }
        }
    });
}

Code coverage describes how well your application is covered by tests. This can be useful to find areas of your app that aren't tested well enough (and could contain potential bugs).

Code Coverage for Unit Tests

Android studio has built-in support to generate code coverage reports from unit tests. We'll look at instrumentation tests in the next section. Note that these reports are independent of each other and are not merged.

Create a run configuration for the unit tests. In the test source set, under java, select the package name: com.example.android.testing.notes. Right click on the package name and select "Create tests in com.example.android.testing.notes". (If you have previously created a test configuration, enable it by clicking on "Select tests in com.example..." In the next step, you need to edit the existing test configuration: Run > Edit configurations )

On the next dialog, select the "Code Coverage" tab.

Select the "IntelliJ IDEA" coverage runner. Accept the (empty) default setting for the "packages and classes to record coverage data". (You could go ahead and exclude the platform generated files (R.java and BuildConfig.java) by specifying the packages in the app specifically, but that's not required.)

Click OK to create the build configuration.

Next, run the code coverage report. In the build configuration, select the new build configuration we have just created, and click the code coverage button to generate the report.

When the task finishes, the code coverage report will be displayed in a panel in the right hand side. (You may have to expand the panel by dragging on its side):

On this panel you can navigate through each feature (which is contained in its own package) and you can even drill down all the way to exact methods. For example, navigate to the notedetail package (com > example > android > testing > notes) to see code coverage for the notes detail feature. Clicking on a class opens the editor for that file - code coverage is displayed in red and green bars on the left hand side:

Code Coverage for Instrumentation Tests

There is no support for showing code coverage reports for instrumentation tests in Android Studio.

First we need to update our gradle build configuration to enable code coverage reports for debug builds.

Open the file app/build.gradle and comment in the following line within the android section.

app/build.gradle

buildTypes {
    debug {
        // Run code coverage reports by default on debug builds.
        testCoverageEnabled = true
    }
}

This enables a new gradle task that will generate a code coverage report for debug builds.

In Android Studio, sync your gradle configuration with your project ( in the toolbar) to see the new *CoverageReport gradle tasks.

Next, run the gradle task createMockDebugAndroidTestCoverageReport to generate the coverage report. You can run it from the Gradle Tasks perspective (on the right hand side of the screen in Android Studio) double click on verification > createMockDebugAndroidTestCoverageReport:

Or you can run it directly from the commandline:

./gradlew createMockDebugAndroidTestCoverageReport

Once the task is complete, you can find the coverage report in app/build/reports/coverage/mock/debug.

Open the file index.html in your web browser. (In Android Studio right click on the file index.html and select Open in Browser > Chrome).

The report will look similar to this:

You can explore the coverage of individual packages and files by navigating through the elements.

Go to the NotePresenter class (under com.example.android.testing.notes.addnote > AddNotePresenter):

Click on a method to see a graphical representation of the parts of the code that are covered by our instrumentation tests:

Congratulations!

You have now learned the basics of app architecture design using the MVP (Model-View-Presenter) pattern and explored the world of testing on Android!

In this codelab we have covered testing from a few different angles - from functional unit tests (with the help of Mockito to handle Android dependencies) to instrumental UI tests with Espresso. Developing an application from a test-driven point of view like we did here can help you define a clean architecture and establish clear communication between different components of your application.

What we've covered

Next Steps

Learn More

Tell us how we did