How to unit test coroutine when it contains coroutine delay?

百般思念 提交于 2019-12-11 04:25:43

问题


When I add a coroutine delay() in my view model, the remaining part of the code will not be executed.

This is my demo code:

class SimpleViewModel : ViewModel(), CoroutineScope {

    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Unconfined

    var data = 0

    fun doSomething() {
        launch {
            delay(1000)
            data = 1
        }
    }
}

class ScopedViewModelTest {

    @Test
    fun coroutineDelay() {
        // Arrange
        val viewModel = SimpleViewModel()

        // ActTes
        viewModel.doSomething()

        // Assert
        Assert.assertEquals(1, viewModel.data)
    }
}

I got the assertion result:

java.lang.AssertionError: 
Expected :1
Actual   :0

Any idea how to fix this?


回答1:


You start a coroutine which suspends for 1 second before setting data to 1. Your test just invokes doSomething but does not wait until data is actually being set. If you add another, longer delay, to the test it will, work:

@Test     
fun coroutineDelay() = runBlocking {
    ...
    viewModel.doSomething()
    delay(1100)
    ...
}

You can also make the coroutine return a Deferred which you can wait on:

fun doSomething(): Deferred<Unit> {
    return async {
        delay(1000)
        data = 1
    }
}

With await there's no need to delay your code anymore:

val model = SimpleViewModel()
model.doSomething().await()



回答2:


The first issue in your code is that SimpleViewModel.coroutineContext has no Job associated with it. The whole point of making your view model a CoroutineScope is the ability to centralize the cancelling of all coroutines it starts. So add the job as follows (note the absence of a custom getter):

class SimpleViewModel : ViewModel(), CoroutineScope {

    override val coroutineContext = Job() + Dispatchers.Unconfined

    var data = 0

    fun doSomething() {
        launch {
            delay(1000)
            data = 1
        }
    }
}

Now your test code can ensure it proceeds to the assertions only after all the jobs your view model launched are done:

class ScopedViewModelTest {

    @Test
    fun coroutineDelay() {
        // Arrange
        val viewModel = SimpleViewModel()

        // ActTes
        viewModel.doSomething()

        // Assert
        runBlocking {
            viewModel.coroutineContext[Job]!!.children.forEach { it.join() }
        }
        Assert.assertEquals(1, viewModel.data)
    }
}


来源:https://stackoverflow.com/questions/53271646/how-to-unit-test-coroutine-when-it-contains-coroutine-delay

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!