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?
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()
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)
}
}