关于android:如何在没有IdlingResource的Espresso中等待异步任务

How to wait for async task in Espresso without IdlingResource

我有以下浓缩咖啡代码:

1
2
3
4
5
6
7
@Test
fun backgroundWorkDisplaysTextAfterLoading() {
    onView(withId(R.id.btn_next_fragment)).perform(click())
    onView(withId(R.id.btn_background_work)).perform(click())

    onView(withId(R.id.hiddenTextView)).check(matches(isDisplayed()))
}

当点击btn_background_work时,有一个服务调用会在2秒内返回响应,然后hiddenTextview就会变得可见。

如何让 Espresso 等待执行 ViewAssertion ( check(matches(isDisplayed)) ),直到该服务调用返回一个值?服务调用通过 Retrofit 完成,服务调用在 Schedulers.io() 线程上完成。

我不能接触生产代码,所以在生产代码中实现 IdlingResources 是不可能的。

感谢任何帮助!


使用空闲资源不需要更新生产中的代码。

但这也可以按照您的要求在没有空闲资源的情况下完成,您可以使用此自定义 ViewAction:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * Perform action of waiting until the element is accessible & not shown.
 * @param viewId The id of the view to wait for.
 * @param millis The timeout of until when to wait for.
 */
public static ViewAction waitUntilShown(final int viewId, final long millis) {
    return new ViewAction() {
        @Override
        public Matcher<View> getConstraints() {
            return isRoot();
        }

        @Override
        public String getDescription() {
            return"wait for a specific view with id <" + viewId +"> is shown during" + millis +" millis.";
        }

        @Override
        public void perform(final UiController uiController, final View view) {
            uiController.loopMainThreadUntilIdle();
            final long startTime = System.currentTimeMillis();
            final long endTime = startTime + millis;
            final Matcher<View> viewMatcher = withId(viewId);

            do {
                for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                    // found view with required ID
                    if (viewMatcher.matches(child) && child.isShown()) {
                        return;
                    }
                }

                uiController.loopMainThreadForAtLeast(50);
            }
            while (System.currentTimeMillis() < endTime);

            // timeout happens
            throw new PerformException.Builder()
                    .withActionDescription(this.getDescription())
                    .withViewDescription(HumanReadables.describe(view))
                    .withCause(new TimeoutException())
                    .build();
        }
    };
}

你可以这样使用它:

1
onView(isRoot()).perform(waitUntilShown(R.id.hiddenTextView, 5000));

如有必要,更新 5 秒(5000 毫秒)的超时时间。


你仍然可以实现 IdlingResource 而不接触生产代码。你只需要创建一个监听 ViewTreeObserver.OnDrawListener:

IdlingResource 回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private class ViewPropertyChangeCallback(private val matcher: Matcher<View>, private val view: View) : IdlingResource, ViewTreeObserver.OnDrawListener {

    private lateinit var callback: IdlingResource.ResourceCallback
    private var matched = false

    override fun getName() ="View property change callback"

    override fun isIdleNow() = matched

    override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
        this.callback = callback
    }

    override fun onDraw() {
        matched = matcher.matches(view)
        callback.onTransitionToIdle()
    }
}

然后创建一个自定义的ViewAction来等待匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
fun waitUntil(matcher: Matcher<View>): ViewAction = object : ViewAction {

    override fun getConstraints(): Matcher<View> {
        return any(View::class.java)
    }

    override fun getDescription(): String {
        return StringDescription().let {
            matcher.describeTo(it)
           "wait until: $it"
        }
    }

    override fun perform(uiController: UiController, view: View) {
        if (!matcher.matches(view)) {
            ViewPropertyChangeCallback(matcher, view).run {
                try {
                    IdlingRegistry.getInstance().register(this)
                    view.viewTreeObserver.addOnDrawListener(this)
                    uiController.loopMainThreadUntilIdle()
                } finally {
                    view.viewTreeObserver.removeOnDrawListener(this)
                    IdlingRegistry.getInstance().unregister(this)
                }
            }
        }
    }
}

并更新您的测试以使用该操作:

1
2
3
4
onView(withId(R.id.hiddenTextView)).perform(waitUntil(isDisplayed()))
// or
onView(withId(R.id.hiddenTextView)).perform(waitUntil(withEffectiveVisibility(VISIBLE)))
    .check(matches(isDisplayed()))

如果没有 IdlingResourceThread.sleep(...) 可能是您的下一个选择,但它会变得不稳定或效率低下。