Transcripted Summary

RecyclerView

The RecyclerView widget is a more advanced, flexible version of ListView.

Because RecyclerView objects work differently than AdapterView objects, onData() cannot be used to interact with them.

To interact with RecyclerView using Espresso, you can use the espresso-contrib package which contains a collection of "RecyclerViewActions" used to perform actions on items.

For example:

  • scrollTo() - Scrolls to the matched View.

  • scrolltoposition() - Scrolls to a specific position.

  • actionOnItem() - Performs a View Action on a matched View.

  • actionOnItemAtPosition() - Performs a ViewAction on a view at a specific position.


RecyclerView (Demo)

In this demo we will learn how to test RecyclerView with an Espresso test.

Begin by running the "RecyclerViewSample" sample application and explore the interface.

In this application we will find a list containing a combination of text and numbers.

We need to scroll inside this list, retrieve test values, and assert that the value is displayed correctly.

Before we begin, check to see that the Espresso dependencies have already been added to the "build.gradle" file.



NOTE

By default, Maven resolves dependencies from the Maven Central Repository.

A common alternative is mvnrepository which contains various libraries such as espresso-core, espresso-idling-resource, espresso-intents, espresso-contrib, and so on.


Now, right-click on the "androidTest" folder and select "New > Java Class". Name it "RecyclerViewSampleTest" and click OK.



We will begin by adding the annotation @RunWith() at the class level to run this test with AndroidJUnit4.class which should already be added to the "build.gradle" file.


@RunWith(AndroidJUnit4.class)
public class RecyclerViewSampleTest {
}

Within the sample application we need to scroll the RecyclerView to a specific element and assert that the text is displayed correctly.



Go to the Tools menu and select the Layout Inspector to open the element inspection view.

Here we see that within the RecyclerView all of the text elements are represented with the same type of TextView object.

We only need a reference to the parent RecyclerView object so locate its ID value within the Properties Table and save a reference to it for later.



Now define a private integer to hold the number to be displayed in the application. We also need to add a string variable to store the element label to be displayed in the application.


@RunWith(AndroidJUnit4.class)
public class RecyclerViewSampleTest {
    // id/recyclerView

    private static final int itemText = 40;
    private static final String displayedText = "This is element#"+ itemText;
}

Next, we can define our ActivityTestRule using the @Rule annotation.


    @Rule
    public ActivityScenarioRule<MainActivity> activityTestRule =
    new ActivityScenarioRule<>(MainActivity.class);

Now, define the test for the RecyclerView using the @Test annotation. Don’t forget to import method statements as necessary.


    @Test
    public void scrollToItem_checkItsText() {
        onView(ViewMatchers.withId(R.id.recyclerView))
    }

Now that we have a view (recyclerView) we need to perform an action on it (RecyclerViewAction) at a specific position.


    @Test
    public void scrollToItem_checkItsText() {
        onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.actionOnItemAtPosition(itemText, click()));
    }

The arguments to actionOnItemAtPosition() identify a specific element as well as an action to be executed.

Finally, assert that the correct label value is displayed including the item text.


    @Test
    public void scrollToItem_checkItsText() {
        onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.actionOnItemAtPosition(itemText, click()));

        onView(withText(displayedText))
            .check(matches(isDisplayed()));
    }

Now that our test class is complete we can run it on our emulated device. Let’s do that now.



It looks like our test failed. Reading the failure message it seems that a "NoMatchingViewException" exception was raised.

This means that no view matched the text that we were trying to match ("This is element# 40").

The problem appears to be a missing space before the pound sign so let’s update that and run the test again


private static final String displayedText = "This is element #"+ itemText;

Now our test should pass.


Testing Toast Messages

A toast message provides simple feedback about an operation in a small popup. It only fills the amount of space required for the message and the current activity remains visible and interactive. Toasts automatically disappear after a timeout period.

Begin by running the "ToastMessageExample" sample application and explore the interface. In this application we find a single button labelled "SHOW TOAST MESSAGE". When we click on this button a toast message displays with the text, "Hello Test Automation University". This toast message will disappear automatically after some time.



For our test we will open the application, click on the button, and verify that the message is displayed.

Before proceeding further we need to implement a custom matcher for the toast message.

Basically, we need to return a toast view matcher to use with Espresso’s onView() method.

In our case we will use a type-safe matcher.

This class evaluates whether or not an on-screen element matches the type defined by the class signature and is non-null.

It is very useful for identifying a screen element by a specific class.

There are two methods that we need to implement for type-safe matchers:

  • MatchesSafely() - Used to implement custom matcher rules using the class found on screen.

  • describeTo() - Describes what the matcher will try to do. It is mainly used for the purpose of verbose logging.


Creating a Custom Matcher (Demo)

In this demo we will learn how to create a custom matcher for a toast message.

As before, right-click on the "androidTest" folder and select "New > Java Class". Name it "ToastMatcher" and click "OK".



Our class will extend the class "TypeSafeMatcher" using <Root> because we will work with the activity and application screens.

We can stub out the previously mentioned matchesSafely() and describeTo() methods by using the Option + Enter shortcut and selecting "Implement methods".




public class ToastMatcher extends TypeSafeMatcher<Root> {
    @Override
    Protected boolean matchesSafely(Root root) {
        return false;
    }
    
    @Override
    public void describeTo(Description description) {
    }
}

Inside describeTo() I will add a line to append the string "is toast". This makes it unambiguous that this is toast-related output.


    @Override
    public void describeTo(Description description) {
       description.appendText("is toast");
    }

The implementation for method matchesSafely() is as follows. Don’t forget to import methods as necessary.


    @Override
    protected boolean matchesSafely(Root root) {
        int type = root.getWindowLayoutParams().get().type;
        if ((type == WindowManager.LayoutParams.TYPE_TOAST)) {
            IBinder windowToken = root.getDecorView().getWindowToken();
            IBinder appToken = root.getDecorView().getApplicationWindowToken();
            return windowToken == appToken;
        }
        return false;
    }

Testing Toast Message (Demo)

Now that we have created our custom matcher for toast messages we will learn how to write a test case for the toast message.

Create the test class by right-clicking on "com.tau.toastdemo" and select "New > Java Class". Call this new class "ToastMessageTest" and click "OK".



Begin by adding the annotation @RunWith() at the class level to run this test with AndroidJUnit4.class which should already be added to the "build.gradle" file.

Next we can add our ActivityScenarioRule using the @Rule annotation.


    @Rule
    public ActivityScenarioRule<MainActivity> rule =
        new ActivityScenarioRule<>(MainActivity.class);

Now we will create our test method testToastMessage() using the annotation @Test.


    @Test
    public void testToastMessage()
    {
    }

For our test we need to click on the button in the application and check if the toast message is displayed.

To implement this we need to know the element IDs.

Go ahead and build the application, run it and bring up the Layout Inspector from the Tools menu.

Highlight the button element and locate its identifier ("buttonToast") from the Properties Table.




    @Test
    public void testToastMessage()
    {
        // id/buttonToast
        onView(withId(R.id.buttonToast))
            .perform(click());
    }

After clicking the button we need to perform an assertion to verify that the toast message is displayed on the screen.

Verify in the application that the contents of the toast message is "Hello Test Automation University" and store this value in a string variable.


    private static final String toastMessage = "Hello from Test Automation University";

Now implement the assertion.


@Test
public void testToastMessage()
{
    // id/buttonToast
    onView(withId(R.id.buttonToast))
        .perform(click());

    onView(withText((toastMessage)))
        .inRoot(new ToastMatcher())
        .check(matches(isDisplayed()));
}

The test is ready to run so go ahead and do that now. The test should pass.


RecyclerViewSampleTest

package com.example.android.testing.espresso.RecyclerViewSample;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import androidx.test.espresso.contrib.RecyclerViewActions;
import androidx.test.espresso.matcher.ViewMatchers;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.filters.LargeTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class RecyclerViewSampleTest {

    private static final int itemText = 40;
    private static final String displayedText = "This is element #"+ itemText;

    @Rule
    public ActivityScenarioRule<MainActivity> activityTestRule =
        new ActivityScenarioRule<>(MainActivity.class);

    @Test
    public void scrollToItem_checkItsText() {
        onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions
                .actionOnItemAtPosition(itemText, click()));

    onView(withText(displayedText))
        .check(matches(isDisplayed()));
    }
}

ToastMatcher

package com.tau.toastdemo;

import android.os.IBinder;
import android.view.WindowManager;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
import androidx.test.espresso.Root;

class ToastMatcher extends TypeSafeMatcher<Root> {

    @Override
    public void describeTo(Description description) {
       description.appendText("is toast");
    }

    @Override
    protected boolean matchesSafely(Root root) {
        int type = root.getWindowLayoutParams().get().type;
        if ((type == WindowManager.LayoutParams.TYPE_TOAST)) {
            IBinder windowToken = root.getDecorView().getWindowToken();
            IBinder appToken = root.getDecorView().getApplicationWindowToken();
            return windowToken == appToken;
        }
        return false;
    }
}

MainActivityTest

package com.tau.toastdemo;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class MainActivityTest
{
    private static final String toastMessage = "Hello from Test Automation University";

    @Rule
    public ActivityScenarioRule<MainActivity> rule =
        new ActivityScenarioRule<>(MainActivity.class);

    @Test
    public void testToastMessage()
    {
        onView(withId(R.id.buttonToast))
            .perform(click());

        onView(withText((toastMessage)))
            .inRoot(new ToastMatcher())
            .check(matches(isDisplayed()));
    }
}


Resources



Quiz

The quiz for this chapter can be found in section 4.4

© 2021 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR