kotlintestingkotlin-coroutines

Testing a function that creates a coroutine scope inside it


I have something like the following in Kotlin:

class A {
    fun a(): Unit = TODO()
}
fun foo(a: A) {
    // Do something
    CoroutineScope(Dispatchers.IO).launch { 
      delay(1000)
      a.a() 
    }
    // Do something else
}

I want to write a test that verifies that the method A.a() was called by foo():

class MyTest {
  @Test
  fun `weird test`() {
    val mockA: A = mockk(relaxed = true)

    foo(mockA)

    verify(exactly = 1) { mockA.a() }
  }
}

Clearly, the test is failing because it is not synchronized with the coroutine. I tried to use runTest, but foo() is not a suspending function, and it is hiding the creation of the scope inside it.

What can I do?


Solution

  • Pass CoroutineDispatcher to the tested method instead of creating them inside the tested method.

    You can create a controllable CoroutineDispatcher - StandardTestDispatcher(this.testScheduler) using coroutineScope provided by runTest.
    And explicitly complete all coroutines on the test dispatcher - advanceUntilIdle() before running your verifications or assertions.

    class MyTest {
        @Test
        fun `weird test`() = runTest {
            val mockA: A = mockk(relaxed = true)
    
            foo(mockA, StandardTestDispatcher(this.testScheduler))
    
            advanceUntilIdle()
            verify(exactly = 1) { mockA.a() }
        }
    }
    
    class A {
        fun a(): Unit = TODO()
    }
    
    fun foo(a: A, dispatcher: CoroutineDispatcher) {
        // Do something
        CoroutineScope(dispatcher).launch {
            delay(1000)
            a.a()
        }
        // Do something else
    }
    

    Also you can pass CoroutineScope instead of CoroutineDispatcher.
    But note that CoroutineScope gives access to coroutines lifecycle (e.g. it could be used to cancel coroutines)

    So:

    The issue usually addressed in Android development: