我正在为我的viewModel编写单元测试,但是在执行测试时遇到了麻烦。 runBlocking { ... }
块实际上并不等待内部代码完成,这对我来说是令人惊讶的。
测试失败,因为result
是null
。为什么runBlocking { ... }
不以阻塞的方式运行ViewModel中的launch
块?
我知道如果我将它转换为返回async
对象的Deferred
方法,那么我可以通过调用await()
来获取对象,或者我可以返回Job
并调用join()
。但是,我想通过将我的ViewModel方法保留为void
函数来实现这一点,有没有办法做到这一点?
// MyViewModel.kt
class MyViewModel(application: Application) : AndroidViewModel(application) {
val logic = Logic()
val myLiveData = MutableLiveData<Result>()
fun doSomething() {
viewModelScope.launch(MyDispatchers.Background) {
System.out.println("Calling work")
val result = logic.doWork()
System.out.println("Got result")
myLiveData.postValue(result)
System.out.println("Posted result")
}
}
private class Logic {
suspend fun doWork(): Result? {
return suspendCoroutine { cont ->
Network.getResultAsync(object : Callback<Result> {
override fun onSuccess(result: Result) {
cont.resume(result)
}
override fun onError(error: Throwable) {
cont.resumeWithException(error)
}
})
}
}
}
// MyViewModelTest.kt
@RunWith(RobolectricTestRunner::class)
class MyViewModelTest {
lateinit var viewModel: MyViewModel
@get:Rule
val rule: TestRule = InstantTaskExecutorRule()
@Before
fun init() {
viewModel = MyViewModel(ApplicationProvider.getApplicationContext())
}
@Test
fun testSomething() {
runBlocking {
System.out.println("Called doSomething")
viewModel.doSomething()
}
System.out.println("Getting result value")
val result = viewModel.myLiveData.value
System.out.println("Result value : $result")
assertNotNull(result) // Fails here
}
}
正如其他人所提到的,runblocking只会阻止在其范围内启动的协同程序,它与viewModelScope是分开的。您可以做的是注入MyDispatchers.Background并将mainDispatcher设置为使用dispatchers.unconfined。
您遇到的问题不是来自runBlocking,而是来自LiveData没有传播没有附加观察者的值。
我已经看到了很多处理这个问题的方法,但最简单的方法就是使用observeForever
和CountDownLatch
。
@Test
fun testSomething() {
runBlocking {
viewModel.doSomething()
}
val latch = CountDownLatch(1)
var result: String? = null
viewModel.myLiveData.observeForever {
result = it
latch.countDown()
}
latch.await(2, TimeUnit.SECONDS)
assertNotNull(result)
}
这种模式很常见,您可能会看到许多项目在某些测试实用程序类/文件中作为函数/方法的某些变体,例如,
@Throws(InterruptedException::class)
fun <T> LiveData<T>.getTestValue(): T? {
var value: T? = null
val latch = CountDownLatch(1)
val observer = Observer<T> {
value = it
latch.countDown()
}
latch.await(2, TimeUnit.SECONDS)
observeForever(observer)
removeObserver(observer)
return value
}
你可以这样打电话:
val result = viewModel.myLiveData.getTestValue()
其他项目使其成为断言库的一部分。
Here is a library有人致力于LiveData测试。
您可能还想查看Kotlin Coroutine CodeLab
或者以下项目:
https://github.com/googlesamples/android-sunflower
https://github.com/googlesamples/android-architecture-components