【问题标题】:How to wait for async task in Espresso without IdlingResource如何在没有 IdlingResource 的情况下在 Espresso 中等待异步任务
【发布时间】:2020-01-10 21:15:41
【问题描述】:

我有以下浓缩咖啡代码:

@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 是不可能的。

感谢任何帮助!

【问题讨论】:

    标签: android testing android-espresso ui-testing


    【解决方案1】:

    您仍然可以在不接触生产代码的情况下实现IdlingResource。你只需要创建一个IdlingResource 回调来监听ViewTreeObserver.OnDrawListener:

    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 等待匹配:

    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)
                    }
                }
            }
        }
    }
    

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

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

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

    【讨论】:

      【解决方案2】:

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

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

      /**
       * 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();
              }
          };
      }
      

      你可以这样使用它:

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

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

      【讨论】:

        猜你喜欢
        • 2013-07-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-25
        • 1970-01-01
        • 2013-11-16
        • 2014-09-06
        相关资源
        最近更新 更多