1. Welcome
Introduction
As a developer, it's important that you test user interactions within your app to make sure that your users don't encounter unexpected results or have a poor experience with your app.
You can test an app's user interface (UI) manually by running the app and trying the UI. But for a complex app, you couldn't cover all the permutations of user interactions within all the app's functionality. You would also have to repeat these manual tests for different device configurations in an emulator, and on different hardware devices.
When you automate tests of UI interactions, you save time, and your testing is systematic. You can use suites of automated tests to perform all the UI interactions automatically, which makes it easier to run tests for different device configurations. To verify that your app's UI functions correctly, it's a good idea to get into the habit of creating UI tests as you code.
Espresso is a testing framework for Android that makes it easy to write reliable UI tests for an app. The framework, which is part of the Android Support Repository, provides APIs for writing UI tests to simulate user interactions within the app—everything from clicking buttons and navigating views to selecting menu items and entering data.
What you should already know
You should be able to:
- Create and run apps in Android Studio.
- Check for the Android Support Repository and install it if necessary.
- Create and edit UI elements using the layout editor and XML.
- Access UI elements from your code.
- Add a click handler to a
Button
.
What you'll learn
- How to set up Espresso in your app project.
- How to write Espresso tests.
- How to test for user input and check for the correct output.
- How to find a spinner, click one of its items, and check for the correct output.
- How to use the Record Espresso Test function in Android Studio.
What you'll do
- Modify a project to create Espresso tests.
- Test the app's text input and output.
- Test clicking a spinner item and check its output.
- Record an Espresso test of a
RecyclerView
.
2. App overview
In this practical you modify the TwoActivities project from a previous lesson. You set up Espresso in the project for testing, and then you test the app's functionality.
The TwoActivities app lets a user enter text in a text field and tap the Send button, as shown on the left side of the figure below. In the second Activity
, the user views the text they entered, as shown on the right side of the figure below.
3. Task 1: Set up Espresso in your project
To use Espresso, you must already have the Android Support Repository installed with Android Studio. You may also need to configure Espresso in your project.
In this task you check to see if the repository is installed. If it is not, you will install it.
1.1 Check for the Android Support Repository
- Download the TwoActivitiesLifecycle project from an earlier codelab on creating and using an
Activity
. - Open the project in Android Studio, and choose Tools > Android > SDK Manager.
The Android SDK Default Preferences pane appears.
- Click the SDK Tools tab and expand Support Repository.
- Look for Android Support Repository in the list.
If Installed appears in the Status column, you're all set. Click Cancel.
If Not installed or Update Available appears, click the checkbox next to Android Support Repository. A download icon should appear next to the checkbox. Click OK.
- Click OK again, and then Finish when the support repository has been installed.
1.2 Configure Espresso for the project
When you start a project for the phone and tablet form factor using API 15: Android 4.0.3 (Ice Cream Sandwich) as the minimum SDK, Android Studio automatically includes the dependencies you need to use Espresso. To execute tests, Espresso and UI Automator use JUnit as their testing framework. JUnit is the most popular and widely-used unit testing framework for Java. Your test class using Espresso or UI Automator should be written as a JUnit 4 test class.
If you have created your project in a previous version of Android Studio, you may have to add the dependencies and instrumentation yourself. To add the dependencies yourself, follow these steps:
- Open the TwoActivities project, or if you prefer, make a copy of the project first and then open the copy.
- Open the build.gradle (Module: app) file.
- Check if the following is included (along with other dependencies) in the
dependencies
section:
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation
'com.android.support.test.espresso:espresso-core:3.0.1'
If the file doesn't include the above dependency statements, enter them into the dependencies
section.
- Android Studio also adds the following instrumentation statement to the end of the
defaultConfig
section of a new project:
testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
If the file doesn't include the above instrumentation statement, enter it at the end of the defaultConfig
section.
Instrumentation is a set of control methods, or hooks, in the Android system. These hooks control an Android component independently of the component's normal lifecycle. They also control how Android loads apps. Using instrumentation makes it possible for tests to invoke methods in the app, and modify and examine fields in the app, independently of the app's normal lifecycle.
- If you modified the build.gradle (Module: app) file, click the Sync Now link in the notification about Gradle files in top right corner of the window.
1.3 Turn off animations on your test device
To let Android Studio communicate with your device, you must first turn on USB Debugging on your device, as described in the lesson on installing and running apps.
Android phones and tablets display animations when moving between apps and screens. The animations are attractive when using the device, but they slow down performance, and may also cause unexpected results or may lead your test to fail. So it's a good idea to turn off animations on your physical device. To turn off animations on your test device, tap on the Settings icon on your physical device. Look for Developer Options. Now look for the Drawing section. In this section, turn off the following options:
- Window animation scale
- Transition animation scale
- Animator duration scale
4. Task 2: Test for Activity switching and text input
You write Espresso tests based on what a user might do while interacting with your app. The Espresso tests are classes that are separate from your app's code. You can create as many tests as you need in order to interact with the View
elements in your UI that you want to test.
The Espresso test is like a robot that must be told what to do. It must find the View
you want it to find on the screen, and it must interact with it, such as clicking the View
, and checking the contents of the View
. If it fails to do any of these things properly, or if the result is not what you expected, the test fails.
With Espresso, you create what is essentially a script of actions to take on each View
and check against expected results. The key concepts are locating and then interacting with UI elements. These are the basic steps:
- Match a View: Find a
View
. - Perform an action: Perform a click or other action that triggers an event with the
View
. - Assert and verify the result: Check the state of the
View
to see if it reflects the expected state or behavior defined by the assertion.
Hamcrest (an anagram of "matchers") is a framework that assists writing software tests in Java. To create a test, you create a method within the test class that uses Hamcrest expressions.
With Espresso you use the following types of Hamcrest expressions to help find View
elements and interact with them:
- ViewMatchers: Hamcrest matcher expressions in the
ViewMatchers
class that lets you find aView
in the currentView
hierarchy so that you can examine something or perform some action. - ViewActions: Hamcrest action expressions in the
ViewActions
class that lets you perform an action on aView
found by aViewMatcher
. - ViewAssertions: Hamcrest assertion expressions in the
ViewAssertions
class that lets you assert or check the state of aView
found by aViewMatcher
.
The following shows how all three expressions work together:
- Use a
ViewMatcher
to find aView
:onView(withId(R.id.my_view))
- Use a
ViewAction
to perform an action:.perform(click())
- Use a
ViewAssertion
to check if the result of the action matches an assertion:.check(matches(isDisplayed()))
The following shows how the above expressions are used together in a statement:
onView(withId(R.id.my_view))
.perform(click())
.check(matches(isDisplayed()));
2.1 Run the example test
Android Studio creates a blank Espresso test class when you create the project.
- In the Project > Android pane, open java > com.example.android.twoactivities (androidTest), and open ExampleInstrumentedTest.
The project is supplied with this example test. You can create as many tests as you wish in this folder. In the next step you will edit the example test.
- In the Project > Android pane, open java > com.example.android.twoactivities (androidTest), and open ExampleInstrumentedTest.
- To run the test, right-click (or Control-click) ExampleInstrumentedTest and choose Run ExampleInstrumentedTest from the pop-up menu. You can then choose to run the test on the emulator or on your device.
The Run pane at the bottom of Android Studio shows the progress of the test, and when finishes, it displays "Tests ran to completion." In the left column Android Studio displays "All Tests Passed".
2.2 Define a class for a test and set up the Activity
You will edit the example test rather than add a new one. To make the example test more understandable, you will rename the class from ExampleInstrumentedTest
to ActivityInputOutputTest
. Follow these steps:
- Right-click (or Control-click) ExampleInstrumentedTest in the Project > Android pane, and choose Refactor > Rename.
- Change the class name to ActivityInputOutputTest, and leave all options checked. Click Refactor.
- Add the following to the top of the
ActivityInputOutputTest
class, before the first@Test
annotation:
@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(
MainActivity.class);
}
The class definition now includes several annotations:
@RunWith
: To create an instrumented JUnit 4 test class, add the@RunWith(AndroidJUnit4.class)
annotation at the beginning of your test class definition.@Rule
: The@Rule
annotation lets you add or redefine the behavior of each test method in a reusable way, using one of the test rule classes that the Android Testing Support Library provides, such asActivityTestRule
orServiceTestRule
. The rule above uses anActivityTestRule
object, which provides functional testing of a singleActivity
—in this case,MainActivity.class
. During the duration of the test you will be able to manipulate yourActivity
directly, usingViewMatchers
,ViewActions
, andViewAssertions
.@Test
: The@Test
annotation tells JUnit that thepublic void
method to which it is attached can be run as a test case. A test method begins with the@Test
annotation and contains the code to exercise and verify a single function in the component that you want to test.
In the above statement, ActivityTestRule
may turn red at first, but then Android Studio adds the following import
statement automatically:
import android.support.test.rule.ActivityTestRule;
2.3 Test switching from one Activity to another
The TwoActivities app includes:
MainActivity
: Includes thebutton_main
button for switching toSecondActivity
and thetext_header_reply
view that serves as a text heading.SecondActivity
: Includes thebutton_second
button for switching toMainActivity
and thetext_header
view that serves as a text heading.
When you have an app that switches from one Activity
to another, you should test that capability. The TwoActivities app provides a text entry field and a Send button (the button_main
id). Clicking Send launches SecondActivity
with the entered text shown in the text_header
view of SecondActivity
.
But what happens if no text is entered? Will SecondActivity
still appear?
The ActivityInputOutputTest
class of tests will show that the View
elements in SecondActivity
appear regardless of whether text is entered.
Follow these steps to add your tests to ActivityInputOutputTest
:
- Add the beginning of the
activityLaunch()
method toActivityInputOutputTest
. This method will test whether theSecondActivity
View
elements appear when clicking theButton
, and include the@Test
notation on a line immediately above the method:
@Test
public void activityLaunch() {
- Add a combined
ViewMatcher
andViewAction
expression to theactivityLaunch()
method to locate theView
containing thebutton_main
Button
, and include aViewAction
expression to perform a click.
onView(withId(R.id.button_main)).perform(click());
The onView()
method lets you use ViewMatcher
arguments to find View
elements. It searches the View
hierarchy to locate a corresponding View
instance that meets some given criteria—in this case, the button_main
View
. The .perform(click())
expression is a ViewAction
expression that performs a click on the View
.
In the above onView
statement, onView
, withID
, and click
may turn red at first, but then Android Studio adds import statements for them.
- Add another
ViewMatcher
expression to theactivityLaunch()
method to find thetext_header
View
(which is inSecondActivity
), and aViewAction
expression to perform a check to see if theView
is displayed:
onView(withId(R.id.text_header)).check(matches(isDisplayed()));
This statement uses the onView()
method to locate the text_header
View
for SecondActivity
and then check to see if it is displayed after clicking the button_main
Button
. The check()
method may turn red at first, but then Android Studio adds an import
statement for it.
- Add similar statements to test whether clicking the
button_second
Button
inSecondActivity
switches toMainActivity
:
onView(withId(R.id.button_second)).perform(click());
onView(withId(R.id.text_header_reply)).check(matches(isDisplayed()));
- Review the
activityLaunch()
method you just created in theActivityInputOutputTest
class. It should look like this:
@Test
public void activityLaunch() {
onView(withId(R.id.button_main)).perform(click());
onView(withId(R.id.text_header)).check(matches(isDisplayed()));
onView(withId(R.id.button_second)).perform(click());
onView(withId(R.id.text_header_reply)).check(matches(isDisplayed()));
}
- To run the test, right-click (or Control-click) ActivityInputOutputTest and choose Run ActivityInputOutputTest from the pop-up menu. You can then choose to run the test on the emulator or on your device.
As the test runs, watch the test automatically start the app and click the Button
. The SecondActivity
View
elements appear. The test then clicks the Button
in SecondActivity
, and MainActivity
View
elements appear.
The Run window (the bottom pane of Android Studio) shows the progress of the test, and when finishes, it displays "Tests ran to completion." In the left column Android Studio displays "All Tests Passed".
The drop-down menu next to the Run icon in the Android Studio toolbar now shows the name of the test class (
ActivityInputOutputTest
). You can click the Run icon to run the test, or switch to app in the dropdown menu and then click the Run icon to run the app.
2.4 Test text input and output
In this step you will write a test for text input and output. The TwoActivities app uses the editText_main
EditText
for input, the button_main
Button
for sending the input to SecondActivity
, and the TextView
in SecondActivity
that shows the output in the field with the id text_message
.
- Add another
@Test
annotation and a newtextInputOutput()
method to test text input and output:
@Test
public void textInputOutput() {
onView(withId(R.id.editText_main)).perform(typeText("This is a test."));
onView(withId(R.id.button_main)).perform(click());
}
The above method uses a ViewMatcher
to locate the View
containing the editText_main
EditText
, and a ViewAction
to enter the text "This is a test."
It then uses another ViewMatcher
to find the View
with the button_main
Button
, and another ViewAction
to click the Button
.
- Add a
ViewMatcher
to the method to locate thetext_message
TextView
inSecondActivity
, and aViewAssertion
to see if the output matches the input to test that the message was correctly sent—it should match"This is a test."
(Be sure to include the period at the end.)
onView(withId(R.id.text_message))
.check(matches(withText("This is a test.")));
- Run the test.
As the test runs, the app starts and the text is automatically entered as input; the Button
is clicked, and SecondActivity
appears with the text.
The bottom pane of Android Studio shows the progress of the test, and when finished, it displays "Tests ran to completion." In the left column Android Studio displays "All Tests Passed". You have successfully tested the EditText
input, the Send Button
, and the TextView
output.
2.5 Introduce an error to show a test failing
Introduce an error in the test to see what a failed test looks like.
- Change the
matches
check on thetext_message
view from"This is a test."
to"This is a failing test."
:
onView(withId(R.id.text_message)).check(matches(withText("This is a failing test.")));
- Run the test again. This time you will see the message in red, "1 test failed", above the bottom pane, and a red exclamation point next to
textInputOutput
in the left column. Scroll the bottom pane to the message "Test running started" and see that all of the results after that point are in red. The very next statement after "Test running started" is:
android.support.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: 'with text: is "This is a failing test."' doesn't match the selected view.
Expected: with text: is "This is a failing test."
Got: "AppCompatTextView{id=2131165307, res-name=text_message ...
Other fatal error messages appear after the above, due to the cascading effect of a failure leading to other failures. You can safely ignore them and fix the test itself.
Task 2 solution code
See ActivityInputOutputTest.java
in the Android Studio project: TwoActivitiesEspresso
5. Task 3: Test the display of spinner selections
The Espresso onView()
method finds a View
that you can test. This method will find a View
in the current View
hierarchy. But you need to be careful—in an AdapterView
such as a Spinner
, the View
is typically dynamically populated with child View
elements at runtime. This means there is a possibility that the View
you want to test may not be in the View
hierarchy at that time.
The Espresso API handles this problem by providing a separate onData()
entry point, which is able to first load the adapter item and bring it into focus prior to locating and performing actions on any of its children.
PhoneNumberSpinner is a starter app you can use to test a Spinner
. It shows a Spinner
, with the id label_spinner
, for choosing the label of a phone number (Home, Work, Mobile, and Other). It then displays the phone number and Spinner
choice in a TextView
.
The goal of this test is to open the Spinner
, click each item, and then verify that the TextView
text_phonelabel
contains the item. The test demonstrates that the code retrieving the Spinner
selection is working properly, and the code displaying the text of the Spinner
item is also working properly. You will write the test using string resources and iterate through the Spinner
items so that the test works no matter how many items are in the Spinner
, or how those items are worded; for example, the words could be in a different language.
3.1 Create the test method
- Download the PhoneNumberSpinnerEspresso project and then open the project in Android Studio.
- Run the app. Enter a phone number, and choose a label from the
Spinner
as shown on the left side of the figure below. The result should appear in theTextView
and in aToast
message, as shown on the right side of the figure.
- Expand com.example.android.phonenumberspinner (androidTest), and open ExampleInstrumentedTest.
- Rename
ExampleInstrumentedTest
toSpinnerSelectionTest
in the class definition, and add the following:
@RunWith(AndroidJUnit4.class)
public class SpinnerSelectionTest {
@Rule
public ActivityTestRule mActivityRule = new ActivityTestRule<>(
MainActivity.class);
3.2 Access the array used for the Spinner items
You want the test to click each item in the Spinner
based on the number of elements in the array. But how do you access the array?
- Create the
iterateSpinnerItems()
method aspublic
returningvoid
, and assign the array used for theSpinner
items to a new array to use within theiterateSpinnerItems()
method:
@Test
public void iterateSpinnerItems() {
String[] myArray =
mActivityRule.getActivity().getResources()
.getStringArray(R.array.labels_array);
}
In the statement above, the test accesses the array (with the id labels_array
) by establishing the context with the getActivity()
method of the ActivityTestRule
class, and getting a resources instance using getResources()
.
- Assign the length of the array to
size
, and start the beginning of afor
loop using thesize
as the maximum number for a counter.
int size = myArray.length;
for (int i=0; i<size; i++) {
3.3 Locate Spinner items and click on them
- Add an
onView()
statement within thefor
loop to find theSpinner
and click on it:
// Find the spinner and click on it.
onView(withId(R.id.label_spinner)).perform(click());
A user must click the Spinner
itself in order to click any item in the Spinner
, so your test must click the Spinner
first before clicking the item.
- Write an
onData()
statement to find and click aSpinner
item:
// Find the spinner item and click on it.
onData(is(myArray[i])).perform(click());
The above statement matches if the object is a specific item in the Spinner
, as specified by the myArray[i]
array element.
If onData
appears in red, click the word, and then click the red light bulb icon that appears in the left margin. Choose the following in the pop-up menu: Static import method ‘android.support.test.espresso.Espresso.onData'
If is
appears in red, click the word, and then click the red light bulb icon that appears in the left margin. Choose the following in the pop-up menu: Static import method...> Matchers.is (org.hamcrest)
- Add another
onView()
statement to thefor
loop to check to see if the resultingtext_phonelabel
matches theSpinner
item specified bymyArray[i]
.
// Find the text view and check that the spinner item
// is part of the string.
onView(withId(R.id.text_phonelabel))
.check(matches(withText(containsString(myArray[i]))));
If containsString
appears in red, click the word, and then click the red light bulb icon that appears in the left margin. Choose the following in the pop-up menu: Static import method...> Matchers.containsString (org.hamcrest)
- Run the test.
The test runs the app, clicks the Spinner
, and "exercises" the Spinner
—it clicks each Spinner
item from top to bottom, checking to see if the item appears in the text_phonelabel
TextView
. It doesn't matter how many Spinner
items are defined in the array, or what language is used for the Spinner
items—the test performs all of them and checks their output against the array.
The bottom pane of Android Studio shows the progress of the test, and when finished, it displays "Tests ran to completion." In the left column Android Studio displays "All Tests Passed".
Task 3 solution code
See SpinnerSelectionTest.java
in the Android Studio project: PhoneNumberSpinnerEspresso
6. Task 4: Record an Espresso test
Android Studio lets you record an Espresso test, which is useful for generating tests quickly. While recording a test, you use your app as a normal user—as you click through the app UI, editable test code is generated for you. You can also add assertions to check if a View
holds a certain value.
Recording Espresso tests, rather than coding the tests by hand, ensures that your app gets UI test coverage on areas that might take too much time or be too difficult to code by hand.
To demonstrate test recording, you will record a test of the Scorekeeper app created in the practical on using drawables, styles, and themes.
4.1 Open and run the app
- Download the Scorekeeper project that you created in Android fundamentals 5.1: Drawables, styles, and themes.
- Open the Scorekeeper project in Android Studio.
- Run the app to ensure that it runs properly.
The Scorekeeper app consists of two sets of Button
elements and two TextView
elements, which are used to keep track of the score for any point-based game with two players.
4.2 Record the test
- Choose Run > Record Espresso Test, select your deployment target (an emulator or a device), and click OK.
The Record Your Test dialog appears, and the Debugger pane appears at the bottom of the Android Studio window. If you are using an emulator, the emulator also appears.
- On the emulator or device, tap the plus (+)
ImageButton
for Team 1 in the app. The Record Your Test window shows the action that was recorded ("Tap AppCompatImageButton with the content description Plus Button").
- Click Add Assertion in the Record Your Test window. A screenshot of the app's UI appears in a pane on the right side of the window, and the Select an element from screenshot option appears in the dropdown menu. Select the score (1) in the screenshot as the UI element you want to check, as shown in the figure below.
- Choose text is from the second dropdown menu, as shown in the figure below. The text you expect to see (1) is already entered in the field below the dropdown menu.
- Click Save Assertion.
- To record another click, on your emulator or device tap the minus (–)
ImageButton
for Team 1 in the app. The Record Your Test window shows the action that was recorded ("Tap AppCompatImageButton with the content description Minus Button").
- Click Add Assertion in the Record Your Test window. The app's UI appears in the right-side pane as before. Select the score (0) in the screenshot as the UI element you want to check.
- Choose text is from the second dropdown menu, as shown in the figure below. The text you expect to see (0) is already entered in the field below the dropdown menu.
- Click Save Assertion, and then click OK.
- In the dialog that appears, edit the name of the test to ScorePlusMinusTest so that it is easy for others to understand the purpose of the test.
- Android Studio may display a request to add more dependencies to your Gradle Build file. Click Yes to add the dependencies. Android Studio adds the following to the dependencies section of the
build.gradle (Module: app)
file:
androidTestCompile
'com.android.support.test.espresso:espresso-contrib:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
exclude group: 'com.android.support', module: 'support-v4'
exclude group: 'com.android.support', module: 'design'
exclude group: 'com.android.support', module: 'recyclerview-v7'
}
- Expand com.example.android.scorekeeper (androidTest) to see the test, and run the test. It should pass. Run it again, and it should pass again.
The following is the test, as recorded in the ScorePlusMinusTest.java
file:
// ... Package and import statements
@LargeTest
@RunWith(AndroidJUnit4.class)
public class ScorePlusMinusTest {
@Rule
public ActivityTestRule<MainActivity> mActivityTestRule
= new ActivityTestRule<>(MainActivity.class);
@Test
public void scorePlusMinusTest() {
ViewInteraction appCompatImageButton = onView(
allOf(withId(R.id.increaseTeam1),
withContentDescription("Plus Button"),
childAtPosition(
childAtPosition(
withClassName(is("android.widget.LinearLayout")),
0),
3),
isDisplayed()));
appCompatImageButton.perform(click());
ViewInteraction textView = onView(
allOf(withId(R.id.score_1), withText("1"),
childAtPosition(
childAtPosition(IsInstanceOf
.<View>instanceOf(android.widget.LinearLayout.class),
0),
2),
isDisplayed()));
textView.check(matches(withText("1")));
ViewInteraction appCompatImageButton2 = onView(
allOf(withId(R.id.decreaseTeam1),
withContentDescription("Minus Button"),
childAtPosition(
childAtPosition(
withClassName(is("android.widget.LinearLayout")),
0),
1),
isDisplayed()));
appCompatImageButton2.perform(click());
ViewInteraction textView2 = onView(
allOf(withId(R.id.score_1), withText("0"),
childAtPosition(
childAtPosition(IsInstanceOf
.<View>instanceOf(android.widget.LinearLayout.class),
0),
2),
isDisplayed()));
textView2.check(matches(withText("0")));
}
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position "
+ position + " in parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup
&& parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent)
.getChildAt(position));
}
};
}
}
The test uses the ViewInteraction
class, which is the primary interface for performing actions or assertions on View
elements, providing both check()
and perform()
methods. Examine the test code to see how it works:
- Perform: The code below uses a method called
childAtPosition()
, which is defined as a customMatcher
, and theperform()
method to click anImageButton
:
ViewInteraction appCompatImageButton = onView(
allOf(withId(R.id.increaseTeam1),
withContentDescription("Plus Button"),
childAtPosition(
childAtPosition(
withClassName(is("android.widget.LinearLayout")),
0),
3),
isDisplayed()));
appCompatImageButton.perform(click());
- Check whether it matches the assertion: The code below also uses the
childAtPosition()
customMatcher
, and checks to see if the clicked item matches the assertion that it should be"1"
:
ViewInteraction textView = onView(
allOf(withId(R.id.score_1), withText("1"),
childAtPosition(
childAtPosition(
IsInstanceOf
.<View>instanceOf(android.widget.LinearLayout.class),
0),
2),
isDisplayed()));
textView.check(matches(withText("1")));
- Custom
Matcher
: The code above useschildAtPosition()
, which is defined as a customMatcher
:
private static Matcher<View> childAtPosition(
final Matcher<View> parentMatcher, final int position) {
return new TypeSafeMatcher<View>() {
@Override
public void describeTo(Description description) {
description.appendText("Child at position "
+ position + " in parent ");
parentMatcher.describeTo(description);
}
@Override
public boolean matchesSafely(View view) {
ViewParent parent = view.getParent();
return parent instanceof ViewGroup
&& parentMatcher.matches(parent)
&& view.equals(((ViewGroup) parent)
.getChildAt(position));
}
};
}
The custom Matcher
in the above code extends the abstract TypeSafeMatcher
class.
You can record multiple interactions with the UI in one recording session. You can also record multiple tests, and edit the tests to perform more actions, using the recorded code as a snippet to copy, paste, and edit.
Task 4 solution code
See ScorePlusMinusTest.java
in the Android Studio project: ScorekeeperEspresso
7. Coding challenge
You learned how to create a RecyclerView
in another practical. The app lets you scroll a list of words from "Word 1" to "Word 19". When you tap the FloatingActionButton
, a new word appears in the list ("+ Word 20").
Like an AdapterView
(such as a Spinner
), a RecyclerView
dynamically populates child View
elements at runtime. But a RecyclerView
is not an AdapterView
, so you can't use onData()
to interact with list items as you did in the previous task with a Spinner
. What makes a RecyclerView
complicated from the point of view of Espresso is that onView()
can't find the child View
if it is off the screen.
Challenge: Fortunately, you can record an Espresso test of using the RecyclerView
. Record a test that taps the FloatingActionButton
, and check to see if a new word appears in the list ("+ Word 20").
Challenge solution code
See RecyclerViewTest.java
in the Android Studio project: RecyclerViewEspresso
8. Summary
To set up Espresso to test an Android Studio project:
- In Android Studio, check for and install the Android Support Repository.
- Add
dependencies
to the build.gradle (Module: app) file:
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation
'com.android.support.test.espresso:espresso-core:3.0.1'
- Add the following instrumentation statement to the end of the
defaultConfig
section:
testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
Instrumentation is a set of control methods, or hooks, in the Android system.
- On your test device, turn off animations. To do this, turn on USB Debugging. Then in the Settings app, select Developer Options > Drawing. Turn off window animation scale, transition animation scale, and animator duration scale.
To test annotations:
@RunWith(AndroidJUnit4.class)
: Create an instrumented JUnit 4 test class.@Rule
: Add or redefine the behavior of each test method in a reusable way, using one of the test rule classes that the Android Testing Support Library provides, such asActivityTestRule
orServiceTestRule
.@Test
: The@Test
annotation tells JUnit that thepublic void
method to which it is attached can be run as a test case.
To test code:
ViewMatchers
class lets you find a view in the currentView
hierarchy so that you can examine something or perform an action.ViewActions
class lets you perform an action on a view found by aViewMatcher
.ViewAssertions
class lets you assert or check the state of a view found by aViewMatcher
.
To test a spinner:
- Use
onData()
with aView
that is dynamically populated by an adapter at runtime. - Get items from an app's array by establishing the context with
getActivity()
and getting a resources instance usinggetResources()
. - Use
onData()
to find and click each spinner item. - Use
onView()
with aViewAction
andViewAssertion
to check if the output matches the selected spinner item.
9. Related concept
The related concept documentation is in 6.1: UI testing.
10. Learn more
Android Studio documentation:
Android developer documentation:
- Test apps on Android
- Fundamentals of Testing
- Building instrumented unit tests
- Espresso recipes
- The Hamcrest Tutorial
- Hamcrest API and Utility Classes
- Test support APIs
Videos
- Android Testing Support - Android Testing Patterns #1 (introduction)
- Android Testing Support - Android Testing Patterns #2 (onView view matching)
- Android Testing Support - Android Testing Patterns #3 (onData and adapter views)
Other:
- Google Testing Blog: Android UI Automated Testing
- Atomic Object: " Espresso – Testing RecyclerViews at Specific Positions"
- Stack Overflow: " How to assert inside a RecyclerView in Espresso?"
- GitHub: Android Testing Samples
- Google Codelabs: Android Testing Codelab
11. Homework
This section lists possible homework assignments for students who are working through this codelab as part of a course led by an instructor. It's up to the instructor to do the following:
- Assign homework if required.
- Communicate to students how to submit homework assignments.
- Grade the homework assignments.
Instructors can use these suggestions as little or as much as they want, and should feel free to assign any other homework they feel is appropriate.
If you're working through this codelab on your own, feel free to use these homework assignments to test your knowledge.
Build and run an app
Record another Espresso test for the ScorekeeperEspresso app. This test should tap the Night Mode option in the options menu and determine whether the Day Mode option appears in its place. The test should then tap the Day Mode option and determine whether the Night Mode option appears in its place.
Answer these questions
Question 1
Which steps do you perform to test a View
interaction, and in what order? Choose one:
- Match a
View
, assert and verify the result, and perform an action. - Match a
View
, perform an action, and assert and verify the result. - Perform an action, match a view, and assert and verify the result.
- Perform an action, and assert and verify the result.
Question 2
Which of the following annotations enables an instrumented JUnit 4 test class? Choose one:
@RunWith
@Rule
@Test
@RunWith
and@Test
Question 3
Which method would you use to find a child View
in an AdapterView
? Choose one:
onData()
to load the adapter and enable the childView
to appear on the screen.onView()
to load theView
from the currentView
hierarchy.onView().check()
to check the currentView
.onView().perform()
to perform a click on the currentView
.
Submit your app for grading
Guidance for graders
Check that the test meets the following criteria:
- The test appears in the
com.example.android.scorekeeper (androidTest)
folder. - The test automatically switches the app from Day Mode to Night Mode, and then back to Day Mode.
- The test passes more than once.
12. Next codelab
To find the next practical codelab in the Android Developer Fundamentals (V2) course, see Codelabs for Android Developer Fundamentals (V2).
For an overview of the course, including links to the concept chapters, apps, and slides, see Android Developer Fundamentals (Version 2).