Unit test the new Kotlin coroutine StateFlow

It seems that the Android team changed the API and documentation after this thread. You can check it here: Continuous collection

SharedFlow/StateFlow is a hot flow, and, as described in the docs, A shared flow is called hot because its active instance exists independently of the presence of collectors. It means the scope that launches the collection of your flow won’t complete by itself.

To solve this issue, you need to cancel the scope in which collect is called, and as the scope of your test is the test itself, it’s not ok to cancel the test, so what you need is to launch it in a different job.

@Test
fun `Testing a integer state flow`() = runTest {
    val _intSharedFlow = MutableStateFlow(0)
    val intSharedFlow = _intSharedFlow.asStateFlow()
    val testResults = mutableListOf<Int>()

    val job = launch(UnconfinedTestDispatcher(testScheduler)) {
        intSharedFlow.toList(testResults)
    }
    _intSharedFlow.value = 5

    assertEquals(2, testResults.size)
    assertEquals(0, testResults.first())
    assertEquals(5, testResults.last())
    job.cancel()
}

Improved case:
TestScope.backgroundScope ensures that the coroutine gets cancelled before the end of the test.

@Test
fun `Testing an integer state flow`() = runTest {
    val _intSharedFlow = MutableStateFlow(0)
    val intSharedFlow = _intSharedFlow.asStateFlow()
    val testResults = mutableListOf<Int>()

    backgroundScope.launch(UnconfinedTestDispatcher(testScheduler)) {
        intSharedFlow.toList(testResults)
    }
    _intSharedFlow.value = 5

    assertEquals(2, testResults.size)
    assertEquals(0, testResults.first())
    assertEquals(5, testResults.last())
}

A few important things:

  1. Always cancel your created job to avoid java.lang.IllegalStateException: This job has not completed yet
  2. As this is a StateFlow, when you start collecting (inside toList), you receive the last state. But if you first start collecting and then call your function viewModel.getUserWallets(), the result list will then have all the states, in case you want to test it too.
  3. The runTest API changed a little bit, and we need to use UnconfinedTestDispatcher(testScheduler) in the context of the launch call. The documentation says: Notice how UnconfinedTestDispatcher is used for the collecting coroutine here. This ensures that the collecting coroutine is launched eagerly and is ready to receive values after launch returns.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)