Search code examples
androidkotlinkotlinx.coroutines

Kotlin coroutines unit testing with runBlocking does not wait for execution


Is there any way to wait for a suspending function that is running in a scope, like what runBlocking does for its running suspending functions?

For example,

class CoroutineTestCase : CoroutineScope {
    val job = Job()
    var value = 1
    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Unconfined


    fun testFunction() {
         async {
             delay(2000)
             value = 2
        }
    }
}

@Test
fun testCoroutine() = runBlocking {
    val coroutineTestCase = CoroutineTestCase()
    coroutineTestCase.testFunction()
    assertEquals(2, coroutineTestCase.value)
}

The above test fails with value being 1 and not changed (since the launch was not being waited to finish). If the testFunction had been a suspending function and I ran it with runBlocking inside my unit test, everything would have worked.

I've tried with other custom dispatchers (like the one below) that can blockingly run their tasks, but no luck

class TestUiContext : CoroutineDispatcher() {
     override fun dispatch(context: CoroutineContext, block: Runnable) {
         block.run()
    }
}

Solution

  • Okay so I figured out what is happening. The launch is not awaited because its returned value is never used.

    In the example above, the testFunction should return the returned value of launch, which is a Deffered object that can be awaited/joined. So to actually wait for its completion, the code has to be changed as below:

    class CoroutineTestCase : CoroutineScope {
        val job = Job()
        var value = 1
        override val coroutineContext: CoroutineContext
            get() = job + Dispatchers.Unconfined
    
    
        fun testFunction(): Deferred<Unit> {
             return async {
                     delay(20000)
                     value = 2
            }
        }
    }
    
    @Test
    fun testCoroutine() = runBlocking {
        val coroutineTestCase = CoroutineTestCase()
        coroutineTestCase.testFunction().await()
        assertEquals(2, coroutineTestCase.value)
    }
    

    Currently the only problem is that, in this case, it actually delays 20 seconds (with the unconfined dispatcher).