'Android:Espresso: How get item on specific position of RecyclerView?

Android Studio 3.2.

I have activity TradesActivity that show list of items (Trader). This list is show by RecyclerView.

I need to write the next Espresso test. if trader.getRunning() == true then background color of item is red. Else background color is green. I find trader by position.

So my Espresso test must to the next steps:

  1. Get Trader of specific position (e.g. 6)
  2. Check trader.running
  3. if it true then check background of textView

How I can do this by Espresso?

Here my solution. Is this a good solution?

    @Rule
    @JvmField
    var activityActivityTestRule = ActivityTestRule(TradersActivity::class.java)

@Test
fun itemList_itemContainerBackgrounColor() {
    // scroll to position
    onView(withId(R.id.tradersRecyclerView))
            .perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(CHECK_ITEM_LIST_POS, swipeLeft()))
    // check
    onView(withId(R.id.tradersRecyclerView)).check(hasCorrectBackgroundColorAtPosition(CHECK_ITEM_LIST_POS, R.id.itemContainer))
}

snippet of my custom ViewAssertion:

class TraderViewAssertion {

    companion object {
        fun hasCorrectBackgroundColorAtPosition(position: Int, @IdRes resId: Int): ViewAssertion {
            return object : ViewAssertion {

                override fun check(view: View, exception: NoMatchingViewException) {
                    if (view !is RecyclerView) {
                        throw exception
                    }
                    val trader = (view.adapter as TraderListItemAdapter).getItem(position) as Trader
                    val itemView = view.findViewHolderForAdapterPosition(position)!!.itemView.findViewById<View>(resId) as TextView
                    val itemViewColorDrawable = itemView.getBackground() as ColorDrawable
                    val colorCode = itemViewColorDrawable.color
                    if (trader.isRunning) {
                        if (DateUtil.getDiffMinutes(Date(trader.last_iteration_time), Date()) > 1) {
                            Assert.assertTrue("Wrong color at position $position", (colorCode == R.color.trade_error_color))
                        } else {
                            Assert.assertTrue("Wrong color at position $position", (colorCode == R.color.trade_running_color))
                        }
                    } else {
                        Assert.assertTrue("Wrong color at position $position", (colorCode == R.color.trade_not_running_color))
                    }
                }
            }
        }
    }
}

But I get error:

java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter exception
at com.myproject.custom.assertion.TraderViewAssertion$Companion$hasCorrectBackgroundColorAtPosition$1.check(TraderViewAssertion.kt)
at androidx.test.espresso.ViewInteraction$SingleExecutionViewAssertion.check(ViewInteraction.java:419)
at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:282)
at androidx.test.espresso.ViewInteraction$2.call(ViewInteraction.java:268)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)


Solution 1:[1]

There is the Kotlin version of Daniel Beleza answer:

private fun withIndex(matcher: Matcher<View>, index: Int): Matcher<View> {
    return object : TypeSafeMatcher<View>() {
        private var currentIndex = 0

        override fun describeTo(description: Description?) {
            description?.appendText("with index: ");
            description?.appendValue(index);
            matcher.describeTo(description);
        }

        override fun matchesSafely(view: View?): Boolean {
            return matcher.matches(view) && currentIndex++ == index;
        }
    }
}

Solution 2:[2]

You can do that creating a custom matcher like this:

public static Matcher<View> withIndex(final Matcher<View> matcher, final int index) {
    return new TypeSafeMatcher<View>() {
        int currentIndex = 0;

        @Override
        public void describeTo(Description description) {
            description.appendText("with index: ");
            description.appendValue(index);
            matcher.describeTo(description);
        }

        @Override
        public boolean matchesSafely(View view) {
            return matcher.matches(view) && currentIndex++ == index;
        }
    }
}

Solution 3:[3]

You did not provide enough information to answer your question in full, but this should lead you in right direction. I have to mention I'd rather create specific condition during test and check given list item at given position, then dive into checking both data and view.

Matcher (pseudocode) :

    public static ViewAssertion hasCorrectBackgroundColorAtPosition(int position, @IdRes int id) {
        return new ViewAssertion() {
            @Override
            public void check(View view, NoMatchingViewException exception) {
                if (!(view instanceof RecyclerView)) {
                    throw exception;
                }

                RecyclerView rv = (RecyclerView) view;
                Trader item = ((YourAdapter) rv.getAdapter()).getItem(position);

                View itemView = rv.findViewHolderForAdapterPosition(position).itemView.findViewById(id);

        if(item.running) {
                Assert.assertTrue(
                        "Wrong color at position " + position,
                        itemView.getBackground().getColor() == R.color.colorA
                );
                } else {
                Assert.assertTrue(
                        "Wrong color at position " + position,
                        itemView.getBackground().getColor() == R.color.colorB
                );
                }

            }
        };
    }

How to use:

onView(yourRecyclerView).check(hasCorrectBackgroundColorAtPosition(123, R.id.yourTextViewId));

Solution 4:[4]

I found solution:

 @Test
    fun itemList_itemContainerBackgrounColor() {
        // scroll to position
        onView(withId(R.id.tradersRecyclerView))
                .perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(CHECK_ITEM_LIST_POS, swipeLeft()))
        // check
        val recyclerView = activityActivityTestRule.activity.findViewById<View>(R.id.tradersRecyclerView) as RecyclerView
        val trader = (recyclerView.adapter as TraderListItemAdapter).getItem(CHECK_ITEM_LIST_POS) as Trader
        val itemContainer = recyclerView.findViewHolderForAdapterPosition(CHECK_ITEM_LIST_POS)!!.itemView.findViewById<View>(R.id.itemContainer) as ConstraintLayout
        val colorCode = (itemContainer.background.current as ColorDrawable).color
        if (trader.isRunning) {
            if (DateUtil.getDiffMinutes(Date(trader.last_iteration_time), Date()) > 1) {
                Assert.assertTrue("Wrong color at position $CHECK_ITEM_LIST_POS", (colorCode == ContextCompat.getColor(itemContainer.context, R.color.trade_error_color)))
            } else {
                Assert.assertTrue("Wrong color at position $CHECK_ITEM_LIST_POS", (colorCode == ContextCompat.getColor(itemContainer.context, R.color.trade_running_color)))
            }
        } else {
            Assert.assertTrue("Wrong color at position $CHECK_ITEM_LIST_POS", (colorCode == ContextCompat.getColor(itemContainer.context, R.color.trade_not_running_color)))
        }
    }

Solution 5:[5]

In my case, I needed to check for the background tint. I modified the solution @ror(https://stackoverflow.com/a/55658980/1713366) provided into this:

fun itemHasBackgroundTintAtPosition(position: Int, @IdRes id: Int, backgroundTintHex : String): ViewAssertion
    {
        return ViewAssertion { view, _ ->

            val itemView: View = (view as RecyclerView).findViewHolderForAdapterPosition(position)!!.itemView.findViewById(id)

            Assert.assertTrue(
                "Wrong background tint at position $position",
                itemView.backgroundTintList?.defaultColor == Color.parseColor(backgroundTintHex)
            )
        }
    }

FYI, this is part of a RecylerView matcher class I created, so there's no need to validate if it's the correct instance.

To use:

onView(withId(R.id.recyclerViewId)).check(itemHasBackgroundTintAtPosition(0, R.id.recyclerViewItemViewId, "some hex value. For example, #000000."))

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ElOjcar
Solution 2 ElOjcar
Solution 3 ror
Solution 4 Alexei
Solution 5 Jcorretjer