android - 由于数据绑定(bind),Espresso 测试失败

标签 android android-espresso

这是我的 View 模型类:

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" />

这里是绑定(bind)适配器:

@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),它会起作用。如何解决?

最佳答案

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

关于android - 由于数据绑定(bind),Espresso 测试失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58482891/

相关文章:

android - 无法导入静态 android.support.test.espresso.contrib.DrawerMatchers.isOpen;

android - 让 Espresso 等待 WebView 完成加载

android - MVP 适配器数据缓存

java - viewpager 中的 Android 应用程序 fragment 无缘无故分离

Android gradle 再次复制 LICENSE.txt 文件

Android Espresso 如何从自定义 View 中获取 EditText 然后键入文本

android - libgdx 如何检测键盘存在

java - Android setVisibility(View.Visible)不适用于布局

Android Espresso 在测试后保持应用运行

android - 什么 id 名称约定对 Kotlin android 扩展有好处