让Espresso等待网络调用,数据库工作和其他异步任务

时间:2019-05-17 15:44:54

标签: android rx-java rx-java2 android-espresso

我有一个使用UseCases访问存储库的应用程序。 UseCases在线程池上订阅,存储库使用OkHttp的线程池发出网络请求,并且存储库还写入使用Schedulers.io()线程池的数据库。

对于某些版本的Android,我无法使用Espresso进行UI测试,因为在所有网络请求/数据库写操作完成之前,我的空闲资源似乎都处于空闲状态。

AFAIK,我的设置使用了三个不同的线程池。我的想法是,如果在我的UI测试中创建了一个空闲资源来监视每个线程池,那么当我通过UseCase调用存储库时,Espresso将等到所有线程池都处于空闲状态后再进行操作测试代码。

这是我的测试代码的示例:

    val wrapped: IdlingResourceScheduler = Rx2Idler.wrap(application.component.providesIoScheduler(), "IO Scheduler")
    val otherWrapped: IdlingResourceScheduler = Rx2Idler.wrap(application.component.providesJobScheduler(), "J O B")
    val resource = arrayOf<IdlingResource>(okHttpIdlingResource, wrapped, otherWrapped)
    idlingRegistry.goIdle(*resource) {
        onView(withId(R.id.toolbar_main))
                .check(matches(isDisplayed()))
    }

我想我在上面的代码中所做的是为数据库线程池,OkHttp线程池和UseCase线程池注册一个空闲资源。因此,我的想法是,当对我的存储库进行调用时,这些线程池中的至少一个将始终处于“活动”状态,并且一旦存储库返回其值,所有线程池都将处于空闲状态,而Espresso会知道它是可以进行测试。就像我在上面说的那样,这对于运行棉花糖(甚至更高版本)的设备似乎是正确的,但不适用于Pie上的设备。

这是UseCase的简化示例:

repository
    .authenticate(params.username, params.password)
    .subscribeOn(My Use Case Thread Pool)
    .observeOn(Main Thread)

这是我的Repository类中auth方法的简化示例:

Single.zip(networkLayer.authenticate(username, password), 
    userDao.getUserInfo(), 
    BiFunction { t1, t2 -> Result(t1,t2) }
    .flatMap {
         Single.just(deleteAllDataFromDatabase())
             .flatMap {
                 networkLayer.getRestOfData()
             }
    }

我真正不明白的是,为什么测试在某些版本上而不是其他版本上运行。我也无法理解为什么,如果我观察所有线程池的空闲情况,那么Espresso如何推进测试?至少我的一个线程池不会做某事(从网络中获取,写入数据库或在用例中做某事?我缺少什么?

1 个答案:

答案 0 :(得分:0)

您需要将所有异步逻辑移到同一线程中。您可以使用针对RxJava / RxAndroid的测试规则来做到这一点

class ImmediateSchedulersRule : TestRule {
    override fun apply(base: Statement, description: Description): Statement {
        return object : Statement() {
            @Throws(Throwable::class)
            override fun evaluate() {
                RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
                RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
                RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
                RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
                try {
                    base.evaluate()
                } finally {
                    RxJavaPlugins.reset()
                    RxAndroidPlugins.reset()
                }
            }
        }
    }
}

并将此规则添加到测试类中:

@get:Rule
var immediateRule = ImmediateSchedulersRule()

顺便说一句,如果您可以重构用例,则更好的解决方案是将Scheduler对象传递给每个用例。

类似的东西:

将调度程序保存在自己的文件中并创建一个默认文件:

interface AppScheduler {
    fun io(): Scheduler
    fun computation(): Scheduler
    fun main(): Scheduler
}

class DefaultAppScheduler : AppScheduler {
    override fun io() = Schedulers.io()
    override fun computation() = Schedulers.computation()
    override fun main() = AndroidSchedulers.mainThread()
}

您的用例已经注入了AppScheduler(或者,如果您不使用Dagger或类似工具,则手动传递),并且在常规应用中,您将通过DefaultAppScheduler实现。

class TestUseCase @Inject constructor(
    private val scheduler: AppScheduler,
    private val yourRepository: YourRepository
) : BaseUseCase<Unit, List<Object>>() {

    //I have omitted some details here but you can get the point
    fun createObservable(params: Unit): Flowable<List<Object>> {
        return yourRepository.loadDataList()
            .subscribeOn(scheduler.io())
    } 
}

然后,在测试代码中,您将创建一个新的AppScheduler实现,仅用于使用trampoline一个进行测试:

class TestAppScheduler : AppScheduler {
    override fun io() = Schedulers.trampoline()
    override fun computation() = Schedulers.trampoline()
    override fun main() = Schedulers.trampoline()
}

通过这种方式,您的测试将等待进行。