'Kotlin flows junit test freezes when to test collect method

I'm trying to write a component which uses different datasources of data. Then data is combined and emitted in the different resulting flow.

class TaskControlComponent(
    private val diskCacheDataSource: DiskCacheDataSource,
    private val debugDataSource: DebugDataSource
) {

    private val _localTasks = MutableStateFlow<Map<String, TaskItem>>(emptyMap())
    val localTasks: StateFlow<Map<String, TaskItem>> = _localTasks

    suspend fun loadLocal() {
        flowOf(
            diskCacheDataSource.defaultFeatures,
            diskCacheDataSource.localFeatures,
            debugDataSource.debugFeatures
        ).flattenMerge().collect {
            computeLocalTasks()
        }
    }

    private suspend fun computeLocalTasks() {
        val resultTasks = HashMap<String, TaskItem>(64)
        listOf(
            diskCacheDataSource.defaultTasks,
            diskCacheDataSource.localTasks,
            debugDataSource.debugTasks
        ).forEach { tasksMap ->
            tasksMap.value.forEach { entry ->
                resultTasks[entry.key] = entry.value
            }
        }
        _localTasks.emit(resultTasks)


      }
    
    }

DataSource

interface DiskCacheDataSource {

    val defaultTasks: StateFlow<Map<String, TaskItem>>
    val localTasks: StateFlow<Map<String, TaskItem>>

}

It works, but how to write junit test for that?

class TaskControlImplTest {

    private lateinit var taskControl: TaskControlComponent

    @Mock
    lateinit var diskCacheDataSource: DiskCacheDataSource

    @Mock
    lateinit var debugDataSource: DebugDataSource

    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        taskControl = TaskControlComponent(diskCacheDataSource, debugDataSource)
    }

    @Test
    fun testFeatureControl() {
        whenever(diskCacheDataSource.defaultTasks).thenReturn(
            MutableStateFlow(
                mapOf(
                    "1" to TaskItem(
                        "1",
                        TaskStatus.On
                    )
                )
            )
        )
        whenever(diskCacheDataSource.localTasks).thenReturn(MutableStateFlow(emptyMap()))
        whenever(debugDataSource.debugTasks).thenReturn(MutableStateFlow(emptyMap()))

        runBlocking {
            taskControl.loadLocal()
        }

        runBlocking {
            taskControl.localTasks.collect {
                Assert.assertEquals(it.size, 1)
            }
        }
    }

}

In case of the following sequence of commands

runBlocking {
    taskControl.loadLocal()
}

runBlocking {
    taskControl.localTasks.collect {
        Assert.assertEquals(it.size, 1)
    }
}

test freezes, and runs forewer.

When I swap the pieces of code, first instead of second and the contrary

runBlocking {
    featureControl.localFeatures.collect {
        Assert.assertEquals(it.size, 1)
    }
}

runBlocking {
    featureControl.loadLocal()
}

Tests finishes with warning

expected:<0> but was:<1>
Expected :0
Actual   :1

Is it possible to write test for such usecase? What should be investigated or done in order to make test workable?



Solution 1:[1]

The reason the order matters here is because StateFlow is hot, unlike normal Flow, meaning it starts running with data immediately not when it is collected

I test with the turbine library but you don't need it. I don't remember the exact setup of not using turbine but it was a bit more complicated so I chose to use turbine

https://github.com/cashapp/turbine

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 John