Write Instrumentation Tests

1. Before you begin

In a previous codelab, you learned how to create and run unit tests. This codelab focuses on instrumentation tests. You will get the opportunity to see what they look like and how to write them.

Prerequisites

  • You have created a project in Android Studio.
  • You have some experience writing code in Android Studio.
  • You have some experience navigating projects in Android Studio.
  • You have written simple unit tests in Android Studio.

What you'll learn

  • What instrumentation tests look like.
  • How to run instrumentation tests.
  • How to write instrumentation tests.

What you need

  • A computer with Android Studio installed.
  • The solution code for the Tip Time app.

Download the starter code for this codelab

In this codelab you will add instrumentation tests to the Tip Time app from a previous solution code.

  1. Navigate to the provided GitHub repository page for the project.
  2. Verify that the branch name matches the branch name specified in the codelab. For example, in the following screenshot the branch name is main.

1e4c0d2c081a8fd2.png

  1. On the GitHub page for the project, click the Code button, which brings up a popup.

1debcf330fd04c7b.png

  1. In the popup, click the Download ZIP button to save the project to your computer. Wait for the download to complete.
  2. Locate the file on your computer (likely in the Downloads folder).
  3. Double-click the ZIP file to unpack it. This creates a new folder that contains the project files.

Open the project in Android Studio

  1. Start Android Studio.
  2. In the Welcome to Android Studio window, click Open.

d8e9dbdeafe9038a.png

Note: If Android Studio is already open, instead, select the File > Open menu option.

8d1fda7396afe8e5.png

  1. In the file browser, navigate to where the unzipped project folder is located (likely in your Downloads folder).
  2. Double-click on that project folder.
  3. Wait for Android Studio to open the project.
  4. Click the Run button 8de56cba7583251f.png to build and run the app. Make sure it builds as expected.

2. Starter app overview

The Tip Time app consists of one screen that shows the user a text input to enter a bill amount, radio buttons to select tip percentage, and a button to calculate the tip.

3. Create the instrumentation test directory

The starter code for this codelab is fully functional, but it is lacking both tests and testing directories. Before we write tests of any kind we need to add a directory for instrumentation tests. Once you have downloaded the starter code, perform the following steps to add a class for your instrumentation tests.

  1. The easiest way to do this is to first switch from the Android view to the Project view. In the project pane at the top left, click the drop down that says Android and select Project.

20055afdc5d38e69.png

  1. Your project view will now look like this:

7e933e8dd546458e.png

  1. Click the first drop down, then drill down to app -> src.

65dd6a80920623ef.png

  1. Right-click on src and select New -> Directory.

73b014c84f89b0f0.png

  1. You should see this window appear:

921d8a0df6310383.png

  1. Select androidTest/java.

c4b99c44611ae609.png

  1. You will now see the androidTest directory in the project structure.

5f6643d80a7ef0f8.png

  1. Right-click on the java directory and select New -> Package.

8f8a590b7bdc01b3.png

  1. You will see the resulting window:

4d154746de968ccf.png

  1. In the window, type the text below and press Return:
com.example.tiptime
  1. Your project window should look like this:

7bb036a4bc3be441.png

  1. Finally, right-click com.example.tiptime and select New -> Kotlin Class/File.

26ff162c120e18d1.png

  1. In the resulting window, type CalculatorTests, select Class from the dropdown, and press Return.

4. Writing Your First Instrumentation Test

Now it's time to write an instrumentation test. The following steps test the functionality for a 20% tip.

  1. Open the file you just created, it should look like this:
package com.example.tiptime

class CalculatorTests {
}
  1. Instrumentation tests require an InstrumentationTestRunner, which allows the test to execute on a device or emulator. There are several other instrumentation runners, but for this test we'll use the AndroidJUnit4 test runner. To specify the test runner, we need to annotate the class with the following:
@RunWith(AndroidJUnit4::class)
class CalculatorTests {
}

Just in case, these are the imports that you need:

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.runner.RunWith

Everything is now set up to begin writing the testing logic.

  1. The Tip Time app consists of a single activity, MainActivity. In order to interact with the activity, your test case must first launch it. Add the following inside the CalculatorTests class.
@get:Rule() 
val activity = ActivityScenarioRule(MainActivity::class.java)

ActivityScenarioRule comes from the AndroidX Test library. It tells the device to launch an activity specified by the developer. You'll need to annotate it with @get:Rule, which specifies that the subsequent rule, in this case launching an activity, should execute before every test in the class. Test rules are an essential tool for testing, and eventually you will learn how to write your own.

  1. Next, you need to write the test logic itself. Create a function and call it calculate_20_percent_tip() and annotate it with the @Test annotation.
@Test
fun calculate_20_percent_tip() {
}

Espresso

This course primarily uses Espresso for instrumentation tests. Espresso is a library that comes ready to use with an Android project when created with Android Studio, and lets you interact with UI components through code.

At this point, you may have noticed that Android Studio has a lot of autocomplete features. One of the challenging aspects of working with Espresso is that methods don't autocomplete if the library hasn't been imported. This can make it difficult to navigate the available methods in Espresso without researching documentation. Throughout these lessons we will be providing you with the methods needed to complete the tests.

First, you need to write code to enter the bill amount in the Cost of Service input text view. Navigating to app -> src -> main -> res -> layout -> activity_main.xml indicates that the ID of the TextInputEditText is cost_of_service_edit_text. Copy the ID name, which we will need later for the test.

a113fb63b50f7674.png

Implement the Test function

Now, in the calculate_20_percent_tip() function in your test class, you can write the test logic.

  1. The first step is to find a UI component to interact with, in this case the TextInputEditText, using the onView() function. The onView() function takes a ViewMatcher object parameter. A ViewMatcher is essentially a UI component that matches a particular criteria, which in this case is a component that has the ID R.id.cost_of_service_edit_text.

The function withId() returns a ViewMatcher that is the UI component with the ID that is passed to it. onView() returns a ViewInteraction, which is an object that we can interact with as if we were controlling the device ourselves. To input some text, you call perform() on the ViewInteraction. Then, perform() takes a ViewAction object. There are a number of methods that return a ViewAction but for now we are going to use the typeText() method. In activity_main.xml we can see that the default tip option is 20%, so for the time being, you don't need to specify which tip option to select.

onView(withId(R.id.cost_of_service_edit_text))
    .perform(typeText("50.00"))

After which, the entire instruction would look like the following:

onView(withId(R.id.cost_of_service_edit_text))
    .perform(typeText("50.00"))
    .perform(ViewActions.closeSoftKeyboard())
  1. The text is now entered and the test needs to click the Calculate button. The code for this follows a format similar to the one we used to enter text. The UI component is different, and so the id name that is passed to the withId() function is different. However, the only difference in the approach is that the ViewAction is different; the click() function is used instead of typeText().
onView(withId(R.id.calculate_button))
    .perform(click())
  1. Finally, you need to make an assertion that the correct tip is displayed. We expect the tip amount to be $10.00. For this test, make sure that the TextView with ID tip_result contains the expected tip value in string form.
onView(withId(R.id.tip_result))
    .check(matches(withText(containsString("$10.00"))))

Select the following imports when prompted:

import androidx.test.espresso.assertion.ViewAssertions.matches
import org.hamcrest.Matchers.containsString

Here, you used a different interaction called check(), which takes a ViewAssertion. You can think of a ViewAssertion as a special Espresso assertion used for UI components. The assertion is that the content of the TextView matches with text that contains the string "$10.00".

Before you run the test, make sure that your imports and code are correct, it should look something like this (it's okay if your imports are in a different order):

package com.example.tiptime

import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.hamcrest.Matchers.containsString
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class CalculatorTests {

   @get:Rule()
   val activity = ActivityScenarioRule(MainActivity::class.java)

   @Test
   fun calculate_20_percent_tip() {
       onView(withId(R.id.cost_of_service_edit_text))
           .perform(typeText("50.00"))

       onView(withId(R.id.calculate_button)).perform(click())

       onView(withId(R.id.tip_result))
           .check(matches(withText(containsString("$10.00"))))
   }
}

If you are using an emulator, make sure that you can see both the emulator and your Android Studio window at the same time. Run the test the same way that you run unit tests (by right-clicking the red/green arrow button to the left of the function, and selecting the first option) and watch what happens!

36684dfa8a17a2c9.gif

You can see that the test executes as if someone was interacting with the app itself!

5. Expand Your Test Suite

Congratulations! You got your first instrumentation test to work!

If you are up for a challenge, you can expand the test suite by adding functions that test the other tip percentages. Use the same format as the function we wrote above, the only changes you need to make is to write the code to select a different percentage option, and to change the value passed into containsString() to account for the different expected results. Don't forget that there is also an option to round up. You can toggle the round up switch by finding the component by its ID, as we demonstrated with onView(withId()), and clicking it.

6. Solution Code

7. Summary

  • Android Studio generates the necessary test classes when the project is created. However, if you encounter a project that doesn't have them, you can create them manually.
  • Test rules run before every test in a test class.
  • Espresso is a fundamental component of instrumentation tests. It enables interaction with UI components using code.

This was a long lesson, give yourself a pat on the back for a job well done!

Learn More