'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:
- Get Trader of specific position (e.g. 6)
- Check
trader.running - 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 |
