Thursday, April 9, 2015

Discovering Espresso for Android: wading through the hierarchical thicket

The Android Lollipop update brought a hard nut to crack for the testers who use Espresso, represented by RecyclerView.

So far the app I'm testing contains this element in couple of places. While I was writing tests for them I found out that it was not so easy and obvious. Thanks to Espresso authors, version 2.0 has the basic RecyclerView actions support which is honestly not enough and is not convenient sometimes.

The example of RecyclerView action:
import static android.support.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
.
.
    @Test
    public void testSomething() {
        onView(withId(R.id.recycler_view_id)).perform(actionOnItemAtPosition(1, click()));
    }
The activity under test has the following structure - it contains the RecyclerView element which is populated with some data. Let's call it the Feed. Each element in Feed is the Feed Post represented by FrameLayout. Of course every FrameLayout has the same layout elements inside as it's neighbors. It contains header with text and footer with like ImageView and TextView used to show number of likes.

What I just described represented by below image:



Now let's try to implement this test case:
  1. click the like ImageView in 3rd RecyclerView item (FameLayout 3), 
  2. check that number of likes changed after the click. 

In my tests I don't want to rely on the Post position in the RecyclerView, because I run multiple tests in parallel and some of them can create new Posts and push down the target one. I'd like to rely on some unique element of the Post instead.

Now I'll focus on the layout. Target ImageView and TextView are marked in red color.


Actually I can't click the like image (ImageView with id=like_image) directly, based on it's id, since this will lead us to multiple matches issue.

But what if we climb in the FrameLayout hierarchy up (from like ImageView) until we found any element with the unique value. In our case the unique is the text in Post header (TextView with id=header_text).

Now we'll find the way how to tie like image with header text. We'll go up in the hierarchy from like image using withParent() method until we reach footer (LinearLayout with id=footer)  which is the sibling of header (LinearLayout with id=header), which contains child with unique text (follow arrows on the image). And the code is:
private void clickOnLikeIconOnPostWithText(String postText){
    onView(allOf(withParent(
            allOf(withParent(
                    allOf(hasSibling(
                            allOf(withId(R.id.header), withChild(withText(postText)))),
                        withId(R.id.footer))),
                withId(R.id.footer_like))),
        withId(R.id.like_image))).perform(click());
}
And the similar code for checking the like text value (TextView with id=like_text):
private void checkLikesText(Matcher<View> likeTextMatcher, String postText){
    onView(allOf(withParent(
            allOf(withParent(
                    allOf(hasSibling(
                            allOf(withId(R.id.header), withChild(withText(postText)))),
                        withId(R.id.footer))),
                withId(R.id.footer_like))),
        withId(R.id.like_text))).check(matches(likeTextMatcher));
}
Finally the usage is:
@Test
public void testPostLike() {
    //like - 1 like count
    clickOnLikeIconOnPostWithText("UNIQUE_TEXT_3");
    checkLikesText(withText("1"),"UNIQUE_TEXT_3");
}
Looks a bit unusual and complicated but it works.