java - RecyclerView Espresso 测试因 RunTimeException 失败

标签 java android android-recyclerview android-espresso

我正在使用以下代码,尝试设置 Espresso:

import android.support.test.espresso.Espresso;
import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import static android.support.test.espresso.action.ViewActions.click;

@RunWith(AndroidJUnit4.class)
public class EspressoTest {

    @Rule
    public ActivityTestRule<MainActivity> firstRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void testRecyclerViewClick() {
        Espresso.onView(ViewMatchers.withId(R.id.recycler_view_ingredients)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
    }
}

它不会成功运行,我不明白为什么。以下是错误:

Caused by: java.lang.RuntimeException: Action will not be performed because the target view does not match one or more of the following constraints:
(is assignable from class: class android.support.v7.widget.RecyclerView and is displayed on the screen to the user)
Target view: "RecyclerView{id=2131165335, res-name=recycler_view_ingredients, visibility=VISIBLE, width=1440, height=0, has-focus=true, has-focusable=true, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=true, is-focusable=true, is-layout-requested=false, is-selected=false, layout-params=android.support.constraint.ConstraintLayout$LayoutParams@caad301, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=0}"
at android.support.test.espresso.ViewInteraction.doPerform(ViewInteraction.java:245)
at android.support.test.espresso.ViewInteraction.access$100(ViewInteraction.java:63)
at android.support.test.espresso.ViewInteraction$1.call(ViewInteraction.java:153)
at android.support.test.espresso.ViewInteraction$1.call(ViewInteraction.java:150)

完整的 Github 存储库: https://github.com/troy21688/KitchenPal

编辑:测试实际上在模拟器上通过,但在我的实际手机(Google Nexus 6)上却没有通过。这让我相信它与每个设备上屏幕尺寸的呈现方式有关。

最佳答案

ID为recycler_view_ingredientsRecyclerView的高度为wrap_content,因此当它没有子项或适配器为空时,高度将为 0。该错误表示不会执行操作,因为目标 View RecyclerView 未显示(height=0),这也意味着数据尚未加载但当时。

您的应用正在不同线程上异步加载数据,然后在完全加载后在主线程上更新您的 RecyclerView。作为一件事情 事实上,Espresso 仅在主线程上同步,因此当您的应用程序开始在后台加载数据时,它会认为应用程序的主线程已空闲,因此它会继续执行该操作,这可能会或可能不会失败取决于设备性能。

解决此问题的一个简单方法是添加一些延迟,例如一秒钟:

Thread.sleep(1000);
onView(withId(R.id.recycler_view_ingredients)).perform(actionOnItemAtPosition(0, click()));

或者,修复它的一种优雅方法是使用 IdlingResource:

onView(withId(R.id.recycler_view_ingredients))
    .perform(
        waitUntil(hasItemCount(greaterThan(0))), // wait until data has loaded
        actionOnItemAtPosition(0, click()));

这里有一些免费类(class):

public static Matcher<View> hasItemCount(Matcher<Integer> matcher) {
    return new BoundedMatcher<View, RecyclerView>(RecyclerView.class) {

        @Override public void describeTo(Description description) {
            description.appendText("has item count: ");
            matcher.describeTo(description);
        }

        @Override protected boolean matchesSafely(RecyclerView view) {
            return matcher.matches(view.getAdapter().getItemCount());
        }
    };
}

public static ViewAction waitUntil(Matcher<View> matcher) {
    return actionWithAssertions(new ViewAction() {
        @Override public Matcher<View> getConstraints() {
            return ViewMatchers.isAssignableFrom(View.class);
        }

        @Override public String getDescription() {
            StringDescription description = new StringDescription();
            matcher.describeTo(description);
            return String.format("wait until: %s", description);
        }

        @Override public void perform(UiController uiController, View view) {
            if (!matcher.matches(view)) {
                LayoutChangeCallback callback = new LayoutChangeCallback(matcher);
                try {
                    IdlingRegistry.getInstance().register(callback);
                    view.addOnLayoutChangeListener(callback);
                    uiController.loopMainThreadUntilIdle();
                } finally {
                    view.removeOnLayoutChangeListener(callback);
                    IdlingRegistry.getInstance().unregister(callback);
                }
            }
        }
    });
}

private static class LayoutChangeCallback implements IdlingResource, View.OnLayoutChangeListener {

    private Matcher<View> matcher;
    private IdlingResource.ResourceCallback callback;
    private boolean matched = false;

    LayoutChangeCallback(Matcher<View> matcher) {
        this.matcher = matcher;
    }

    @Override public String getName() {
        return "Layout change callback";
    }

    @Override public boolean isIdleNow() {
        return matched;
    }

    @Override public void registerIdleTransitionCallback(ResourceCallback callback) {
        this.callback = callback;
    }

    @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        matched = matcher.matches(v);
        callback.onTransitionToIdle();
    }
}

关于java - RecyclerView Espresso 测试因 RunTimeException 失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51851653/

相关文章:

java - 如何在 Heroku 中将 Stanford NLP 工具与 NLTK 一起使用?

android - android中fastboot和recoverymode的区别

android - StaggeredGridLayoutManager 用于创建这样的 View

android - 使用 FlexboxLayoutManager 来查看不同高度的 RecyclerView 项目

android - 清除整个适配器并添加新元素时的 RecyclerView 动画

java - 将 arraylist 的范围转换为字符串数组

java - 与面向对象方法的斗争

java - 有人向我解释了这个 Java Big O 代码的几个步骤

android - Flutter:任务 ':app:compileDebugKotlin' 执行失败

android - 显示周数/日历周