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:

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.