【问题标题】:Solution to pause and resume in RxJava similar to TestCoroutineScope类似于 TestCoroutineScope 的 RxJava 中暂停和恢复的解决方案
【发布时间】:2021-06-30 20:29:49
【问题描述】:

完整的源代码在:https://github.com/AliRezaeiii/StarWarsSearch-RxPaging

这是我在使用协程进行网络时测试 ViewModel 的本地单元测试:

@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
    testCoroutineRule.runBlockingTest {
        `when`(api.fetchShowList()).thenReturn(emptyList())
    }

    val repository = ShowRepository(dao, api, context, TestContextProvider())

    testCoroutineRule.pauseDispatcher()

    val viewModel = MainViewModel(repository)

    assertThat(viewModel.shows.value, `is`(Resource.loading()))

    testCoroutineRule.resumeDispatcher()

    assertThat(viewModel.shows.value, `is`(Resource.success(emptyList())))
}

如您所知,我可以使用 TestCoroutineScope 暂停和恢复,因此我可以测试 liveData 何时处于 Loading 或 Success 状态。

我想知道我们是否可以在使用 RxJava 进行测试时做同样的事情。

目前我只能验证成功状态:

@Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
    `when`(repository.getSpecie(anyString())).thenReturn(Single.just(specie))
    `when`(repository.getPlanet(anyString())).thenReturn(Single.just(planet))
    `when`(repository.getFilm(anyString())).thenReturn(Single.just(film))

    viewModel = DetailViewModel(schedulerProvider, character, 
               GetSpecieUseCase(repository), GetFilmUseCase(repository))

    viewModel.liveData.value.let {
        assertThat(it, `is`(notNullValue()))
        if (it is Resource.Success) {
            it.data?.let { data ->
                assertTrue(data.films.isNotEmpty())
                assertTrue(data.species.isNotEmpty())
            }
        }
    }
}

在 ViewModel 初始化块中,我发送网络请求。您可以在下面的课程中查看它。这可以在使用协程时使用暂停和恢复进行测试。 RxJava 怎么样?

open class BaseViewModel<T>(
    private val schedulerProvider: BaseSchedulerProvider,
    private val singleRequest: Single<T>
) : ViewModel() {

    private val compositeDisposable = CompositeDisposable()

    private val _liveData = MutableLiveData<Resource<T>>()
    val liveData: LiveData<Resource<T>>
        get() = _liveData

    init {
        sendRequest()
    }

    fun sendRequest() {
        _liveData.value = Resource.Loading
        singleRequest.subscribeOn(schedulerProvider.io())
            .observeOn(schedulerProvider.ui()).subscribe({
                _liveData.postValue(Resource.Success(it))
            }) {
                _liveData.postValue(Resource.Error(it.localizedMessage))
                Timber.e(it)
            }.also { compositeDisposable.add(it) }
    }

    override fun onCleared() {
        super.onCleared()
        compositeDisposable.clear()
    }
}

【问题讨论】:

  • 您可以在一定程度上使用TestScheduler,但除非您在流程中设置了定时步骤,否则它将运行所有可用的工作。
  • 感谢您的评论。正如我所理解的,使用 RxJava 的 TestScheduler 时,在您调用 advanceTimeBy() 之前,时间是静止的,但是在 sendRequest 方法位于 ViewModel 的 init 块中的情况下,它如何工作。您能否使用您在我的案例中推荐的代码来解释您的建议作为答案。如您所见,我没有Observable.interval,我不想测试我的代码中的延迟或超时是否按预期工作。
  • 使schedulerProvider.ui() 和schedulerProvider.io() 返回一个您也可以引用的TestScheduler,然后将testCoroutineRule.resumeDispatcher() 替换为testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)。
  • 我试过了。如果我返回 TestScheduler,所有测试用例都将通过而不检查断言中实际值和预期值的结果。那是我错过的吗? Here 是我的测试用例。
  • 你改用我建议的TestScheduler的代码在哪里?

标签: android unit-testing rx-java rx-java2 kotlin-coroutines


【解决方案1】:

没有看到你的尝试,我只能猜测有两个可能的问题需要修复:

  1. 对所有提供者方法使用相同的TestScheduler:

    class ImmediateSchedulerProvider : BaseSchedulerProvider {
    
        val testScheduler = TestScheduler()
    
        override fun computation(): Scheduler = testScheduler
    
        override fun io(): Scheduler = testScheduler
    
        override fun ui(): Scheduler = testScheduler
    }
    
  2. 单元测试并没有因为错误的状态而失败,因此即使代码没有运行,它们似乎也通过了:

    @Test
    fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
        `when`(repository.getSpecie(anyString())).thenReturn(Single.just(specie))
        `when`(repository.getPlanet(anyString())).thenReturn(Single.just(planet))
        `when`(repository.getFilm(anyString())).thenReturn(Single.just(film))
    
        viewModel = DetailViewModel(schedulerProvider, character, GetSpecieUseCase(repository),
                GetPlanetUseCase(repository), GetFilmUseCase(repository))
    
        viewModel.liveData.value.let {
            assertThat(it, `is`(Resource.Loading))
        }
    
        schedulerProvider.testScheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS) // <-------------
    
        viewModel.liveData.value.let {
            assertThat(it, `is`(notNullValue()))
            if (it is Resource.Success) {
                it.data?.let { data ->
                    assertTrue(data.films.isNotEmpty())
                    assertTrue(data.species.isNotEmpty())
                }
            } else {
                fail("Wrong type " + it)  // <---------------------------------------------
            }
        }
    }
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-28
    • 1970-01-01
    • 2010-10-05
    • 2020-12-19
    • 1970-01-01
    • 2010-10-31
    相关资源
    最近更新 更多