Эспрессо - Проверьте, что элементы RecyclerView упорядочены правильно
Как проверить, отображаются ли элементы RecyclerView в правильном порядке с помощью Espresso? Я пытаюсь проверить это, проверяя текст по названию каждого элемента.
Когда я пробую этот кусок кода, он работает, чтобы щелкнуть по элементу, но не может перейти к нему вместо выполнения щелчка, пытающегося утвердить текст для элемента
onView(withId(R.id.rv_metrics)).perform(actionOnItemAtPosition(0, click()));
Когда я пытаюсь использовать пользовательское сопоставление, я получаю ошибку
Error performing 'load adapter data' on view 'with id: mypackage_name:id/rv_metrics'
теперь я знаю onData doesn't work for RecyclerView
но до этого я пытался использовать пользовательские сопоставления для этой задачи.
public static Matcher<Object> hasTitle(final String inputString) {
return new BoundedMatcher<Object, Metric>(Metric.class) {
@Override
protected boolean matchesSafely(Metric metric) {
return inputString.equals(metric.getMetric());
}
@Override
public void describeTo(org.hamcrest.Description description) {
description.appendText("with title: ");
}
};
}
Я также пробовал что-то подобное, но это, очевидно, не работает из-за типа, заданного в качестве параметра для метода actionOnItemAtPosition, но будет ли у нас что-то похожее на это, которое может работать?
onView(withId(R.id.rv_metrics)).check(actionOnItemAtPosition(0, ViewAssertions.matches(withText("Weight"))));
Что мне здесь не хватает, пожалуйста? Большое спасибо.
3 ответа
Как уже упоминалось здесь, RecyclerView
объекты работают иначе, чем AdapterView
объекты, так onData()
не могут быть использованы для взаимодействия с ними.
Для того, чтобы найти вид на определенную позицию RecyclerView
вам нужно реализовать кастом RecyclerViewMatcher
как ниже:
public class RecyclerViewMatcher {
private final int recyclerViewId;
public RecyclerViewMatcher(int recyclerViewId) {
this.recyclerViewId = recyclerViewId;
}
public Matcher<View> atPosition(final int position) {
return atPositionOnView(position, -1);
}
public Matcher<View> atPositionOnView(final int position, final int targetViewId) {
return new TypeSafeMatcher<View>() {
Resources resources = null;
View childView;
public void describeTo(Description description) {
String idDescription = Integer.toString(recyclerViewId);
if (this.resources != null) {
try {
idDescription = this.resources.getResourceName(recyclerViewId);
} catch (Resources.NotFoundException var4) {
idDescription = String.format("%s (resource name not found)",
new Object[] { Integer.valueOf
(recyclerViewId) });
}
}
description.appendText("with id: " + idDescription);
}
public boolean matchesSafely(View view) {
this.resources = view.getResources();
if (childView == null) {
RecyclerView recyclerView =
(RecyclerView) view.getRootView().findViewById(recyclerViewId);
if (recyclerView != null && recyclerView.getId() == recyclerViewId) {
childView = recyclerView.findViewHolderForAdapterPosition(position).itemView;
}
else {
return false;
}
}
if (targetViewId == -1) {
return view == childView;
} else {
View targetView = childView.findViewById(targetViewId);
return view == targetView;
}
}
};
}
}
А затем используйте его в своем тестовом примере следующим образом:
@Test
void testCase() {
onView(new RecyclerViewMatcher(R.id.rv_metrics)
.atPositionOnView(0, R.id.txt_title))
.check(matches(withText("Weight")))
.perform(click());
onView(new RecyclerViewMatcher(R.id.rv_metrics)
.atPositionOnView(1, R.id.txt_title))
.check(matches(withText("Height")))
.perform(click());
}
Если кому-то интересна версия Kotlin, вот она
fun hasItemAtPosition(position: Int, matcher: Matcher<View>) : Matcher<View> {
return object : BoundedMatcher<View, RecyclerView>(RecyclerView::class.java) {
override fun describeTo(description: Description?) {
description?.appendText("has item at position $position : ")
matcher.describeTo(description)
}
override fun matchesSafely(item: RecyclerView?): Boolean {
val viewHolder = item?.findViewHolderForAdapterPosition(position)
return matcher.matches(viewHolder?.itemView)
}
}
}
Я немного упростил ответ Мосиуса:
public static Matcher<View> hasItemAtPosition(final Matcher<View> matcher, final int position) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
@Override
public void describeTo(Description description) {
description.appendText("has item at position " + position + ": ");
matcher.describeTo(description);
}
@Override
protected boolean matchesSafely(RecyclerView recyclerView) {
RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForAdapterPosition(position);
return matcher.matches(viewHolder.itemView);
}
};
}
Мы проходим Matcher
к функции, чтобы мы могли предоставить дополнительные условия. Пример использования:
onView(hasItemAtPosition(hasDescendant(withText("Item 1")), 0)).check(matches(isDisplayed()));
onView(hasItemAtPosition(hasDescendant(withText("Item 2")), 1)).check(matches(isDisplayed()));
Первоначальная проблема была решена, но я выкладываю здесь ответ, поскольку библиотека Бариста решает эту проблему в одной строке кода.
assertDisplayedAtPosition(R.id.rv_metrics, 0, R.id.tv_title, "weight");
Он сделан поверх эспрессо и документацию к нему можно найти здесь
Надеюсь, что это может быть полезно для кого-то.:)
Если вы хотите сопоставить совпадения на позиции в RecyclerView
, то вы можете попробовать создать кастом Matcher<View>
:
public static Matcher<View> hasItemAtPosition(int position, Matcher<View> matcher) {
return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {
@Override public void describeTo(Description description) {
description.appendText("has item: ");
matcher.describeTo(description);
description.appendText(" at position: " + position);
}
@Override protected boolean matchesSafely(RecyclerView view) {
RecyclerView.Adapter adapter = view.getAdapter();
int type = adapter.getItemViewType(position);
RecyclerView.ViewHolder holder = adapter.createViewHolder(view, type);
adapter.onBindViewHolder(holder, position);
return matcher.matches(holder.itemView);
}
};
}
И вы можете использовать его, например:
onView(withId(R.id.rv_metrics)).check(matches(0, hasDescendant(withText("Weight")))))