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.
- Navigate to the provided GitHub repository page for the project.
- Verify that the branch name matches the branch name specified in the codelab. For example, in the following screenshot the branch name is main.
- On the GitHub page for the project, click the Code button, which brings up a popup.
- In the popup, click the Download ZIP button to save the project to your computer. Wait for the download to complete.
- Locate the file on your computer (likely in the Downloads folder).
- Double-click the ZIP file to unpack it. This creates a new folder that contains the project files.
Open the project in Android Studio
- Start Android Studio.
- In the Welcome to Android Studio window, click Open.
Note: If Android Studio is already open, instead, select the File > Open menu option.
- In the file browser, navigate to where the unzipped project folder is located (likely in your Downloads folder).
- Double-click on that project folder.
- Wait for Android Studio to open the project.
- Click the Run button
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.
- 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.
- Your project view will now look like this:
- Click the first drop down, then drill down to app -> src.
- Right-click on src and select New -> Directory.
- You should see this window appear:
- Select androidTest/java.
- You will now see the androidTest directory in the project structure.
- Right-click on the java directory and select New -> Package.
- You will see the resulting window:
- In the window, type the text below and press Return:
com.example.tiptime
- Your project window should look like this:
- Finally, right-click com.example.tiptime and select New -> Kotlin Class/File.
- 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.
- Open the file you just created, it should look like this:
package com.example.tiptime
class CalculatorTests {
}
- 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.
- 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 theCalculatorTests
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.
- 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.
Implement the Test function
Now, in the calculate_20_percent_tip()
function in your test class, you can write the test logic.
- The first step is to find a UI component to interact with, in this case the
TextInputEditText
, using theonView()
function. TheonView()
function takes aViewMatcher
object parameter. AViewMatcher
is essentially a UI component that matches a particular criteria, which in this case is a component that has the IDR.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())
- 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 theViewAction
is different; theclick()
function is used instead oftypeText()
.
onView(withId(R.id.calculate_button))
.perform(click())
- 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 IDtip_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!
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!