协程 - 单元测试viewModelScope.launch方法

问题描述 投票:4回答:2

我正在为我的viewModel编写单元测试,但是在执行测试时遇到了麻烦。 runBlocking { ... }块实际上并不等待内部代码完成,这对我来说是令人惊讶的。

测试失败,因为resultnull。为什么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
    }
}

android kotlin android-livedata kotlin-coroutines
2个回答
1
投票

正如其他人所提到的,runblocking只会阻止在其范围内启动的协同程序,它与viewModelScope是分开的。您可以做的是注入MyDispatchers.Background并将mainDispatcher设置为使用dispatchers.unconfined。


-1
投票

您遇到的问题不是来自runBlocking,而是来自LiveData没有传播没有附加观察者的值。

我已经看到了很多处理这个问题的方法,但最简单的方法就是使用observeForeverCountDownLatch

@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

© www.soinside.com 2019 - 2024. All rights reserved.