数据绑定导致Espresso测试失败

时间:2019-10-21 08:55:38

标签: android android-espresso

这是我的viewmodel类:

class MainViewModel(
    private val schedulerProvider: BaseSchedulerProvider,
    private val api : StorytelService
) : BaseViewModel() {

    private val _posts = MutableLiveData<List<Post>>()
    val posts: LiveData<List<Post>>
        get() = _posts

    private val _status = MutableLiveData<Status>()
    val status: LiveData<Status>
        get() = _status

    init {
        showPhotos()
    }

    fun showPhotos() {
        EspressoIdlingResource.increment() // App is busy until further notice
        _status.postValue(Status.LOADING)
        compositeDisposable.add(api.getPhotos()
            .subscribeOn(schedulerProvider.io())
            .observeOn(schedulerProvider.ui())
            .doFinally {
                if (!EspressoIdlingResource.countingIdlingResource.isIdleNow) {
                    EspressoIdlingResource.decrement() // Set app as idle.
                }
            }
            .subscribe({
                _status.postValue(Status.SUCCESS)
                showPosts(it)
            }) {
                _status.postValue(Status.ERROR)
                Timber.e(it)
            })
    }

    private fun showPosts(networkPhotos: List<NetworkPhoto>) {
        EspressoIdlingResource.increment() // App is busy until further notice
        _status.postValue(Status.LOADING)
        compositeDisposable.add(api.getPosts()
            .subscribeOn(schedulerProvider.io())
            .observeOn(schedulerProvider.ui())
            .doFinally {
                if (!EspressoIdlingResource.countingIdlingResource.isIdleNow) {
                    EspressoIdlingResource.decrement() // Set app as idle.
                }
            }
            .subscribe({ networkPosts ->
                _status.postValue(Status.SUCCESS)
                _posts.postValue(
                    PostAndImages(networkPosts, networkPhotos).asDomaineModel()
                )
            }) {
                _status.postValue(Status.ERROR)
                Timber.e(it)
            })
    }

这是我的recyclerView布局:

<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            app:showData="@{vm.status}"
            tools:listitem="@layout/post_item" />

这是绑定适配器:

@BindingAdapter("showData")
fun View.showData(status: Status) {
    visibility = if (status == Status.SUCCESS) View.VISIBLE else View.GONE
}

您注意到我正在使用EspressoIdlingResource,但是当我按照espresso测试运行时,它失败了:

    @Test
    fun shouldBeAbleToLoadList() {
        onView(withId(R.id.recycler_view)).check(matches(isDisplayed()))
    } 

如果我在测试开始时添加Thread.sleep(5000),它将起作用。如何解决?

2 个答案:

答案 0 :(得分:2)

Idling Resource应该有可能,但是它们有些乏味。

我刚刚更新了旧的viewMatcher代码:

/**
 * Perform action of waiting for a specific view id to be displayed.
 * @param viewId The id of the view to wait for.
 * @param millis The timeout of until when to wait for.
 */
public static ViewAction waitDisplayed(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 + "> has been displayed 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> matchId = withId(viewId);
            final Matcher<View> matchDisplayed = isDisplayed();

            do {
                for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                    if (matchId.matches(child) && matchDisplayed.matches(child)) {
                        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();
        }
    };
}

那你应该只做:

@Test
    fun shouldBeAbleToLoadList() {
        onView(isRoot()).perform(waitDisplayed(R.id.recycler_view, 5000));
    } 

5000的超时时间为5秒(5000毫秒),您可以根据需要进行更改。

执行waitDisplayed后,可能会显示该元素或已达到超时。在最后一种情况下,将抛出Exception

答案 1 :(得分:0)

您将需要为绑定创建Idling Resource。 您可以检查Android Architecture Components sample具有类似的实现。这是您需要寻找的东西:

  1. 首先,您将需要添加一个Idling Resource类,该类检查是否有任何待处理的绑定(您可以找到实现here
  2. 现在,您可以创建一条规则,该规则将自动为您注册/注销Idling Resource(您可以找到实现here)。
  3. 现在您可以将此规则添加到测试中,并检查它是否有效(示例测试实现,您可以找到here)。