协程-单元测试viewModelScope.launch方法 [英] Coroutines - unit testing viewModelScope.launch methods

查看:2081
本文介绍了协程-单元测试viewModelScope.launch方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am writing unit tests for my viewModel, but having trouble executing the tests. The runBlocking { ... } block doesn't actually wait for the code inside to finish, which is surprising to me.

测试失败,因为resultnull.为什么runBlocking { ... }不能以阻塞方式运行ViewModel内部的launch块?

The test fails because result is null. Why doesn't runBlocking { ... } run the launch block inside the ViewModel in blocking fashion?

我知道是否将其转换为返回Deferred对象的async方法,然后可以通过调用await()来获取对象,或者可以返回Job并调用join(). 但是,我想通过将ViewModel方法保留为void函数来做到这一点,有没有办法做到这一点?

I know if I convert it to a async method that returns a Deferred object, then I can get the object by calling await(), or I can return a Job and call join(). But, I'd like to do this by leaving my ViewModel methods as void functions, is there a way to do this?

// 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
    }
}

推荐答案

您需要做的是将协同程序的启动包装到具有给定调度程序的块中.

What you need to do is wrap your launching of a coroutine into a block with given dispatcher.

var ui: CoroutineDispatcher = Dispatchers.Main
var io: CoroutineDispatcher =  Dispatchers.IO
var background: CoroutineDispatcher = Dispatchers.Default

fun ViewModel.uiJob(block: suspend CoroutineScope.() -> Unit): Job {
    return viewModelScope.launch(ui) {
        block()
    }
}

fun ViewModel.ioJob(block: suspend CoroutineScope.() -> Unit): Job {
    return viewModelScope.launch(io) {
        block()
    }
}

fun ViewModel.backgroundJob(block: suspend CoroutineScope.() -> Unit): Job {
    return viewModelScope.launch(background) {
        block()
    }
}

在顶部注意ui,io和背景.这里的所有内容都是顶级+扩展功能.

Notice ui, io and background at the top. Everything here is top-level + extension functions.

然后在viewModel中像这样启动协程:

Then in viewModel you start your coroutine like this:

uiJob {
    when (val result = fetchRubyContributorsUseCase.execute()) {
    // ... handle result of suspend fun execute() here         
}

在测试中,您需要在@Before块中调用此方法:

And in test you need to call this method in @Before block:

@ExperimentalCoroutinesApi
private fun unconfinifyTestScope() {
    ui = Dispatchers.Unconfined
    io = Dispatchers.Unconfined
    background = Dispatchers.Unconfined
}

(将其添加到诸如BaseViewModelTest之类的基类中要好得多)

(Which is much nicer to add to some base class like BaseViewModelTest)

这篇关于协程-单元测试viewModelScope.launch方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆