I am using espresso-contrib to perform actions on a RecyclerView
, and it works as it should, ex:
onView(withId(R.id.recycler_view))
.perform(Rec
The solutions above are great, I'm contributing one more in Kotlin.
Espresso needs to scroll to the position and then check it. To scroll to the position, I'm using RecyclerViewActions.scrollToPosition. Then use findViewHolderForAdapterPosition to perform the check. Instead of calling findViewHolderForAdapterPosition
directly, I am using a nicer convenience function childOfViewAtPositionWithMatcher.
In the example code below, Espresso scrolls to row 43 of the RecyclerView
and the checks that the row's ViewHolder
has two TextViews and that those TextViews contain the text "hello text 1 at line 43" and "hello text 2 at line 43" respectively:
import androidx.test.espresso.contrib.RecyclerViewActions
import it.xabaras.android.espresso.recyclerviewchildactions.RecyclerViewChildActions.Companion.childOfViewAtPositionWithMatcher
// ...
val index = 42 // index is 0 based, corresponds to row 43 from user's perspective
onView(withId(R.id.myRecyclerView))
.perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(43))
.check(
matches(
allOf(
childOfViewAtPositionWithMatcher(
R.id.myTextView1,
index,
withText("hello text 1 at line 43")
),
childOfViewAtPositionWithMatcherMy(
R.id.myTextView2,
index,
withText("hello text 2 at line 43")
)
)
)
)
Here are my build.gradle dependencies (newer versions may exist):
androidTestImplementation "androidx.test.espresso:espresso-contrib:3.2.0"
androidTestImplementation "it.xabaras.android.espresso:recyclerview-child-actions:1.0"
Modified matcher solution for when you check if your item(Text in TextView) is first in RecyclerView.
// True if first item in the RV and input text
fun <T> customMatcherForFirstItem(name: String, matcher: Matcher<T>): Matcher<T> {
return object: BaseMatcher<T> () {
var isFirst = true
override fun matches(item: Any): Boolean {
if (isFirst && (item as TextView).text == name) {
isFirst = false
return true
}
return false
}
override fun describeTo(description: Description?) {}
}
}
Usage: (returns true if child textview in the item has the same text as we expect/search)
onView(withText("TEXT_VIEW_TEXT")).check(matches(
customMatcherForFirstItem("TEXT_VIEW_TEXT", withParent(withId(R.id.recycler_view)))))
Based on -> https://proandroiddev.com/working-with-recycler-views-in-espresso-tests-6da21495182c
You should check out Danny Roa's solution Custom RecyclerView Actions And use it like this:
onView(withRecyclerView(R.id.recycler_view)
.atPositionOnView(1, R.id.ofElementYouWantToCheck))
.check(matches(withText("Test text")));
In my case, it was necessary to merge Danny Roa's and riwnodennyk's solution:
onView(withId(R.id.recyclerview))
.perform(RecyclerViewActions.scrollToPosition(80))
.check(matches(atPositionOnView(80, withText("Test Test"), R.id.targetview)));
and the method :
public static Matcher<View> atPositionOnView(final int position, final Matcher<View> itemMatcher,
@NonNull final int targetViewId) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
@Override
public void describeTo(Description description) {
description.appendText("has view id " + itemMatcher + " at position " + position);
}
@Override
public boolean matchesSafely(final RecyclerView recyclerView) {
RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
View targetView = viewHolder.itemView.findViewById(targetViewId);
return itemMatcher.matches(targetView);
}
};
}
For testing all items (include off-screen) use code bellow
Sample:
fun checkThatRedDotIsGone(itemPosition: Int) {
onView(
withId(R.id.recycler_view)).
check(itemViewMatches(itemPosition, R.id.inside_item_view,
withEffectiveVisibility(Visibility.GONE)))
}
Class:
object RecyclerViewAssertions {
fun itemViewMatches(position: Int, viewMatcher: Matcher<View>): ViewAssertion {
return itemViewMatches(position, -1, viewMatcher)
}
/**
* Provides a RecyclerView assertion based on a view matcher. This allows you to
* validate whether a RecyclerView contains a row in memory without scrolling the list.
*
* @param viewMatcher - an Espresso ViewMatcher for a descendant of any row in the recycler.
* @return an Espresso ViewAssertion to check against a RecyclerView.
*/
fun itemViewMatches(position: Int, @IdRes resId: Int, viewMatcher: Matcher<View>): ViewAssertion {
assertNotNull(viewMatcher)
return ViewAssertion { view, noViewException ->
if (noViewException != null) {
throw noViewException
}
assertTrue("View is RecyclerView", view is RecyclerView)
val recyclerView = view as RecyclerView
val adapter = recyclerView.adapter
val itemType = adapter!!.getItemViewType(position)
val viewHolder = adapter.createViewHolder(recyclerView, itemType)
adapter.bindViewHolder(viewHolder, position)
val targetView = if (resId == -1) {
viewHolder.itemView
} else {
viewHolder.itemView.findViewById(resId)
}
if (viewMatcher.matches(targetView)) {
return@ViewAssertion // Found a matching view
}
fail("No match found")
}
}
}
Solution based of this article > Better Android RecyclerView Testing!