Step #3: Testing Our List
Now, add this test function to RosterListFragmentTest
:
@Test
fun testListContents() {
ActivityScenario.launch(MainActivity::class.java)
onView(withId(R.id.items)).check(matches(hasChildCount(3)))
}
While containing only two lines of code (not counting several import
statements), quite a bit is done here.
ActivityScenario.launch()
will start up our MainActivity
, which in turn will display our RosterListFragment
. launch()
will not return until our UI is up and ready for testing. ActivityScenario
comes from the androidx.test.ext:junit
library that we added.
The other line is a fairly typical Espresso statement. Espresso uses a lot of imported functions to try to keep the code terse.
An Espresso statement usually takes one of two forms:
-
onView().check()
, to see if a widget is in a particular state -
onView().perform()
, to perform some action on a widget, such as clicking it
Here, we have a statement that is of the first form, where we want to check()
the state of a widget and confirm that it meets our expectations.
onView()
is the Espresso way of looking up widgets in the current activity’s view hierarchy. It takes a ViewMatcher
as a parameter, where that ViewMatcher
encodes some rule(s) for what widget we want to access. The withId()
function creates a ViewMatcher
that finds a view by its ID, in this case R.id.items
. So, onView(withId(R.id.items))
looks up our RecyclerView
and returns… a ViewInteraction
.
One thing that you can do with a ViewInteraction
is to call check()
on it. check()
takes a ViewAssertion
as a parameter. A ViewAssertion
works a bit like the Kluent assertions that we used in the unit tests, in that it checks our view and will fail the test if the view does not match expectations.
The most common way of getting a ViewAssertion
is to call the matches()
function. This returns a ViewAssertion
wrapped around a Hamcrest Matcher
.
hasChildCount()
is a ViewMatcher
, which is a Matcher
that knows how to “match” some view property against some expected value. hasChildCount()
looks at the number of child widgets of a ViewGroup
and compares it against the expected value. In this case, we are expecting that our RecyclerView
has three rows, because we put three model objects into our test repository. hasChildCount(3)
will return true
if the RecyclerView
has three rows, false
otherwise. So, overall, check(matches(hasChildCount(3)))
will fail the test if the RecyclerView
has anything other than three rows.
If you run the test, the test succeeds. Moreover, if you run the test, you will actually see the activity flash onto the screen for a brief moment, as ActivityScenario.launch()
displays our MainActivity
. This is one of the reasons why instrumented tests are slow: we often are doing a lot of setup work, such as launching an activity.
This is obviously a very limited test of the UI. Unfortunately, Espresso gets very complex very quickly. Trying to do more — such as clicking on a CheckBox
to confirm the repository is updated — will get to be more complex than is suitable for a tutorial.
Note: you might wonder why the function name is testListContents()
and not something like test list contents()
. The backticks style of writing function names works in unit tests but not in instrumented tests, due to some Android limitations.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.