我正试图为视图模型编写一个测试。我正在做一个仪器测试,因为我需要context
。
视图模型和测试看起来像:
class MyViewModel(
private val dispatcher: CoroutineDispatchers = Dispatchers.IO) : ViewModel() {
private val _livedata = MutableLiveData<Boolean>()
val livedata: LiveData<Boolean> = _livedata
fun doSomething() {
viewModelScope.launch(dispatcher) {
//suspend function with retrofit
_livedata.value = true
}
}
}
class MyViewModelTest {
private lateinit var viewModel: MyViewModel
@get:Rule
var mainCoroutineRule = MainCoroutineRule()
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Before
fun setup() {
viewModel = MyViewModel(mainCoroutineRule.dispatcher)
}
@Test
fun testMyViewModel() {
mainCoroutineRule.runBlockingTest {
viewModel.doSomething()
mainCoroutineRule.dispatcher.advanceUntilIdle()
val result = viewModel.livedata.getOrAwaitValue()
assertThat(result).isTrue()
}
}
}
问题是,由于doSomething()
在另一个协程上被调用,并且是异步完成的,因此result
是空的。
我如何运行我的测试,以便挂起函数阻塞线程,以便我的断言在挂起函数完成后捕获result
?
从外面的信息来看,我很困惑。
我认为我不需要InstantTaskExecutorRule()
,因为我正在进行仪器测试?
添加此规则没有帮助:
@ExperimentalCoroutinesApi
class MainCoroutineRule(val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()):
TestWatcher(),
TestCoroutineScope by TestCoroutineScope(dispatcher) {
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
cleanupTestCoroutines()
Dispatchers.resetMain()
}
}
我需要在视图模型中注入一个阻塞主线程的协程调度器吗?
为了解决这个问题,我不得不使用https://medium.com/androiddevelopers/unit-testing-livedata-and-other-common-observability-problems-bb477262eb04
/* Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}
@Suppress("UNCHECKED_CAST")
return data as T
}
如果您要启动协同程序来调用改装服务,您可能需要在IO调度器上执行此操作。您可以将其作为依赖项传入,然后在测试中通过一个测试来控制它
class MyViewModel : ViewModel(private val ioDispatcher: Dispatcher = Dispatchers.IO) {
private val _livedata = MutableLiveData<Boolean>()
val livedata: LiveData<Boolean> = _livedata
fun doSomething() {
viewModelScope.launch(ioDispatcher) {
//suspend function with retrofit
_livedata.value = true
}
}
}
测试:
@Before
fun setup() {
viewModel = MyViewModel(coroutineRule.dispatcher)
}
@Test
fun testMyViewModel() {
mainCoroutineRule.runBlockingTest {
viewModel.doSomething()
coroutineRule.dispatcher.advanceUntilIdle()
val result = viewModel.livedata.value
assertThat(result).isTrue()
}
}