Testing MutableStateFlow without flakiness

April 2021

  1. We need the observable itself:
private val _countdown = MutableStateFlow(0)
val countdown = _countdown.asStateFlow()

2. Now we need some collector, that we’ll need to use for testing:

val scope = CoroutineScope(Job() + Dispatchers.Main)
val countdownObserver = mock<FlowCollector<Int>>()

scope?.launch { countdown.collect(countdownObserver) }

3. Let’s test:

_countdown.value = 0
verify(countdownObserver).emit(0)

_countdown.value = 0
verify(countdownObserver).emit(0)

The complete snippet.

  1. It fails and it’s the correct behaviour.
  2. Dispatchers.Main is the must! Dispatchers.IO will give you a lot of flakiness!
package com.visa.mobile.feature.payments.usecase

import com.nhaarman.mockitokotlin2.clearInvocations
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.visa.mobile.common.TestCoroutineRule
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Rule
import org.junit.Test

@InternalCoroutinesApi
@ExperimentalCoroutinesApi
class MutableStateFlowTest {
    @get:Rule
    var coroutineRule = TestCoroutineRule()

    @Test
    fun `test MutableStateFlow`() = coroutineRule.runBlockingTest {
        val countdown = MutableStateFlow(0)
        val countdownObserver = mock<FlowCollector<Int>>()

        val scope = CoroutineScope(Job() + Dispatchers.Main)

        scope.launch { countdown.collect(countdownObserver) }

        countdown.value = 0; verify(countdownObserver).emit(0)
        clearInvocations(countdownObserver)
        countdown.value = 0; verify(countdownObserver).emit(0)

        scope.cancel()
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *