如何使用 Espresso 访问 RecyclerView ViewHolder? [英] How to access RecyclerView ViewHolder with Espresso?
问题描述
我想测试我的 RecyclerView
的每个 ViewHolder
中包含的文本:
@RunWith(AndroidJUnit4.class)公共类 EspressoTest {私人活动 mMainActivity;私有 RecyclerView mRecyclerView;private int res_ID = R.id.recycler_view_ingredients;私人 int itemCount = 0;//TODO:这条规则与下面的测试相关的目的是什么?@规则公共 ActivityTestRulefirstRule = new ActivityTestRule<>(MainActivity.class);//TODO:对 Espresso 测试和所需的依赖项非常困惑;它出现 Recyclerview//TODO: 除了 Android 文档中提到的依赖项之外,还需要其他依赖项吗?//TODO:测试 RecyclerView 的所有视图的最佳方法是什么?在 RecyclerView 中填充的视图的动态数量是多少?//TODO:来自 StackOverflow 帖子的说明:https://stackoverflow.com/questions/51678563/how-to-test-recyclerview-viewholder-text-with-espresso/51698252?noredirect=1#comment90433415_51698252//TODO:这有必要吗?@前公共无效设置测试(){this.mMainActivity = this.firstRule.getActivity();this.mRecyclerView = this.mMainActivity.findViewById(this.res_ID);this.itemCount = this.mRecyclerView.getAdapter().getItemCount();}@测试公共无效 testRecyclerViewClick() {Espresso.onView(ViewMatchers.withId(R.id.recycler_view_ingredients)).perform(RecyclerViewActions.actionOnItemAtPosition(1, ViewActions.click()));}//不能调用这个方法,依赖不正确@测试公共无效 testRecyclerViewText() {//检查位置 3 的项目有一些内容"onView(withRecyclerView(R.id.scroll_view).atPosition(3)).check(matches(hasDescendant(withText("Some content"))));}}}
下面也是我的 gradle,我一直不明白 RecyclerView 测试需要哪些单独的依赖项:
依赖项{实现文件树(目录:'libs',包括:['*.jar'])实现 'com.android.support:recyclerview-v7:27.1.1'实现 'com.google.android.exoplayer:exoplayer:2.6.1'实现 'com.android.support:appcompat-v7:27.1.1'实现 'com.android.support.constraint:constraint-layout:1.1.2'实现 'com.android.support:support-v4:27.1.1'testImplementation 'junit:junit:4.12'androidTestImplementation 'com.android.support.test:testing-support-lib:0.1'androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.0'androidTestImplementation('com.android.support.test.espresso:espresso-contrib:2.0') {排除组:'com.android.support',模块:'appcompat'排除组:'com.android.support',模块:'support-v4'排除模块:'recyclerview-v7'}实现 'com.android.support:support-annotations:27.1.1'实现 'com.squareup.okhttp3:okhttp:3.10.0'实现 'com.google.code.gson:gson:2.8.2'实现 'com.squareup.retrofit2:converter-gson:2.4.0'实现 'com.squareup.okhttp3:logging-interceptor:3.10.0'androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'实现 'com.android.support:cardview-v7:27.1.1'实现 'com.google.android.exoplayer:exoplayer:2.6.0'}
此外,如果 RecyclerView
动态填充数据会怎样?那么你根本无法对你想要测试的位置进行硬编码......
Espresso package espresso-contrib
是必须的,因为它提供了那些不支持断言的RecyclerViewActions
.
import android.support.test.espresso.contrib.RecyclerViewActions;导入 android.support.test.rule.ActivityTestRule;导入 android.support.test.runner.AndroidJUnit4;进口 org.hamcrest.Description;导入 org.hamcrest.Matcher;导入 org.hamcrest.TypeSafeMatcher;导入 org.junit.Before;导入 org.junit.Rule;导入 org.junit.Test;导入 org.junit.runner.RunWith;导入静态 android.support.test.espresso.Espresso.onView;导入静态 android.support.test.espresso.action.ViewActions.click;导入静态 android.support.test.espresso.assertion.ViewAssertions.matches;导入静态 android.support.test.espresso.matcher.ViewMatchers.withId;导入静态 android.support.test.espresso.matcher.ViewMatchers.withText;@RunWith(AndroidJUnit4.class)公共类测试成分{/** 目标应用程序的活动 */私人成分活动 mActivity;/** {@link RecyclerView} 的资源 ID */private int resId = R.id.recyclerview_ingredients;/** {@link RecyclerView} */私人成分LinearView mRecyclerView;/** 和它的项目计数 */私人 int itemCount = 0;/*** 可以使用这样的 {@link ActivityTestRule},例如.对于 Intent.putExtra(),* 类似的方法是将命令行参数传递给常规运行配置.* 此代码在 {@link FragmentActivity} 启动之前运行.* 还有一个 {@link IntentsTestRule},但这里不需要.**/@规则public ActivityTestRulemActivityRule = new ActivityTestRule(IngredientsActivity.class) {@覆盖受保护的意图 getActivityIntent() {意图意图=新意图();Bundle extras = new Bundle();意图.putExtras(额外);回归意图;}};@前公共无效setUpTest(){/* 从 ActivityTestRule 中获取 Activity */this.mActivity = this.mActivityRule.getActivity();/* 获取 Activity 的 Ui 句柄 */this.mRecyclerView = this.mActivity.findViewById(this.resId);this.itemCount = this.mRecyclerView.getAdapter().getItemCount();}@测试公共无效 RecyclerViewTest() {如果(this.itemCount > 0){for(int i=0; i < this.itemCount; i++) {/* 点击项目 */onView(withId(this.resId)).perform(RecyclerViewActions.actionOnItemAtPosition(i, click()));/* 检查是否正在显示 ViewHolder */onView(新 RecyclerViewMatcher(this.resId).atPositionOnView(i, R.id.cardview)).check(matches(isDisplayed()));/* 检查第一个项目的文本 */如果(我== 0){onView(新 RecyclerViewMatcher(this.resId).atPositionOnView(i, R.id.ingredientName)).check(matches(withText("Farbstoffe")));}}}}}
相反,可以使用
I want to test the text contained in each ViewHolder
of my RecyclerView
:
@RunWith(AndroidJUnit4.class)
public class EspressoTest {
private Activity mMainActivity;
private RecyclerView mRecyclerView;
private int res_ID = R.id.recycler_view_ingredients;
private int itemCount = 0;
//TODO: What is the purpose of this rule as it relates to the Test below?
@Rule
public ActivityTestRule<MainActivity> firstRule = new ActivityTestRule<>(MainActivity.class);
//TODO: Very confused about Espresso testing and the dependencies required; it appears Recyclerview
//TODO: Requires additional dependencies other than those mentioned in the Android documentation?
//TODO: What would be best method of testing all views of RecyclerView? What is there is a dynamic number of Views that are populated in RecyclerView?
//TODO: Instruction from StackOverflow Post: https://stackoverflow.com/questions/51678563/how-to-test-recyclerview-viewholder-text-with-espresso/51698252?noredirect=1#comment90433415_51698252
//TODO: Is this necessary?
@Before
public void setupTest() {
this.mMainActivity = this.firstRule.getActivity();
this.mRecyclerView = this.mMainActivity.findViewById(this.res_ID);
this.itemCount = this.mRecyclerView.getAdapter().getItemCount();
}
@Test
public void testRecyclerViewClick() {
Espresso.onView(ViewMatchers.withId(R.id.recycler_view_ingredients)).perform(RecyclerViewActions.actionOnItemAtPosition(1, ViewActions.click()));
}
//CANNOT CALL THIS METHOD, THE DEPENDENCIES ARE INCORRECT
@Test
public void testRecyclerViewText() {
// Check item at position 3 has "Some content"
onView(withRecyclerView(R.id.scroll_view).atPosition(3))
.check(matches(hasDescendant(withText("Some content"))));
}
}
}
Below is my gradle as well, I never understood what separate dependencies are required for RecyclerView testing:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:recyclerview-v7:27.1.1'
implementation 'com.google.android.exoplayer:exoplayer:2.6.1'
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:support-v4:27.1.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:testing-support-lib:0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.0'
androidTestImplementation('com.android.support.test.espresso:espresso-contrib:2.0') {
exclude group: 'com.android.support', module: 'appcompat'
exclude group: 'com.android.support', module: 'support-v4'
exclude module: 'recyclerview-v7'
}
implementation 'com.android.support:support-annotations:27.1.1'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.google.android.exoplayer:exoplayer:2.6.0'
}
Also, what if the RecyclerView
populates data dynamically? Then you simply could not hard code the position you wanted to test....
Espresso package espresso-contrib
is necessary, because it provides those RecyclerViewActions
, which do not support assertions.
import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class TestIngredients {
/** the Activity of the Target application */
private IngredientsActivity mActivity;
/** the {@link RecyclerView}'s resource id */
private int resId = R.id.recyclerview_ingredients;
/** the {@link RecyclerView} */
private IngredientsLinearView mRecyclerView;
/** and it's item count */
private int itemCount = 0;
/**
* such a {@link ActivityTestRule} can be used eg. for Intent.putExtra(),
* alike one would pass command-line arguments to regular run configurations.
* this code runs before the {@link FragmentActivity} is being started.
* there also would be an {@link IntentsTestRule}, but not required here.
**/
@Rule
public ActivityTestRule<IngredientsActivity> mActivityRule = new ActivityTestRule<IngredientsActivity>(IngredientsActivity.class) {
@Override
protected Intent getActivityIntent() {
Intent intent = new Intent();
Bundle extras = new Bundle();
intent.putExtras(extras);
return intent;
}
};
@Before
public void setUpTest() {
/* obtaining the Activity from the ActivityTestRule */
this.mActivity = this.mActivityRule.getActivity();
/* obtaining handles to the Ui of the Activity */
this.mRecyclerView = this.mActivity.findViewById(this.resId);
this.itemCount = this.mRecyclerView.getAdapter().getItemCount();
}
@Test
public void RecyclerViewTest() {
if(this.itemCount > 0) {
for(int i=0; i < this.itemCount; i++) {
/* clicking the item */
onView(withId(this.resId))
.perform(RecyclerViewActions.actionOnItemAtPosition(i, click()));
/* check if the ViewHolder is being displayed */
onView(new RecyclerViewMatcher(this.resId)
.atPositionOnView(i, R.id.cardview))
.check(matches(isDisplayed()));
/* checking for the text of the first one item */
if(i == 0) {
onView(new RecyclerViewMatcher(this.resId)
.atPositionOnView(i, R.id.ingredientName))
.check(matches(withText("Farbstoffe")));
}
}
}
}
}
Instead one can use a RecyclerViewMatcher for that:
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;
}
}
};
}
}
这篇关于如何使用 Espresso 访问 RecyclerView ViewHolder?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!