'My MutableStateFlow doesnt emit when called from suspend function in test

I am trying to write tests for my Repository which provides access to my Room database. For this, I wrote a Mock Database and a mock DAO:

My Database:

abstract class JoozdlogDatabase protected constructor(): RoomDatabase() {
    abstract fun aircraftTypeDao(): AircraftTypeDao
    // (...)
}
class MockDatabase: JoozdlogDatabase() {
    override fun aircraftTypeDao(): AircraftTypeDao = MockAircraftTypeDao()
}

My DAO:

interface AircraftTypeDao {
    @Query("SELECT * FROM AircraftTypeData")
    suspend fun requestAllAircraftTypes(): List<AircraftTypeData>

    @Query("SELECT * FROM AircraftTypeData")
    fun aircraftTypesFlow(): Flow<List<AircraftTypeData>>
    
    // etc etc //
}
class MockAircraftTypeDao: AircraftTypeDao {
    private val simulatedDatabase = ArrayList<AircraftTypeData>()
    private val simulatedFlow = MutableStateFlow<List<AircraftTypeData>>(listOf(AircraftTypeData("aap", "noot", false, multiEngine = false)))

    override suspend fun requestAllAircraftTypes(): List<AircraftTypeData> = simulatedDatabase

    override fun aircraftTypesFlow(): Flow<List<AircraftTypeData>> = simulatedFlow

    override suspend fun save(vararg aircraftTypeData: AircraftTypeData) {
        //println("${this::class.simpleName} Saving ${aircraftTypeData.size} type data")
        simulatedDatabase.addAll(aircraftTypeData)
        emit()
    }

    override suspend fun clearDb() {
        println("${this::class.simpleName} Clear DB")
        simulatedDatabase.clear()
        emit()
    }

    private fun emit(){
        println("emit() should emit ${simulatedDatabase.size} items")
        simulatedFlow.update { simulatedDatabase.toList() } // also tried: simulatedFlow.value = simulatedDatabase.toList()
        println("simulatedFlow.value is now ${simulatedFlow.value.size}")
    }

My Test data:

object TestData {
    val aircraftTypes = listOf(
        AircraftType("Test Aircraft 1 (MP/ME)", "TAC1", multiPilot = true, multiEngine = true),
        AircraftType("Test Aircraft 2 (SP/SE)", "TAC2", multiPilot = false, multiEngine = false)
    )
}

and my test:

@Test
fun test() {
    runTest {
        var currentTypesList: List<AircraftType> = emptyList()
        val aircraftRepository = AircraftRepository.mock(MockDatabase())
            // DispatcherProvider.default() provides UnconfinedTestDispatcher(TestCoroutineScheduler()) for my test.
        launch(DispatcherProvider.default()) {
            aircraftRepository.aircraftTypesFlow.collect {
                println("emitted ${it.size} flights: $it")
                currentTypesList = it
            }
        }
        aircraftRepository.replaceAllTypesWith(TestData.aircraftTypes)
        delay(500)
        println("Done waiting")
        assertEquals (2, currentTypesList.size)
    }
}

Expected result: Test passed. received result: java.lang.AssertionError: expected:<2> but was:<1> for the single assert

received output:

emitted 1 flights: [AircraftType(name=aap, shortName=noot, multiPilot=false, multiEngine=false)]
MockAircraftTypeDao Clear DB
emit() should emit 0 items
simulatedFlow.value is now 0
emit() should emit 2 items
simulatedFlow.value is now 2
Done waiting

Now, I have been at this all morning and I just don't get why it won't collect anything but the first value. Things I tried:

Making a flow object to test my collector -> collector is OK

Accessing the flow item in DAO directly -> Does not work

Setting value of MutableStateFlow with update and with value = -> neither works.

Making a different flow object the is exactly the same but not called from a suspend function: Works.

So, I guess something about the calling suspend function is doing something wrong, but the Flow object is being updated before the delay is over, and it just won't collect. If anybody is much smarter than me and can explain what I am doing wrong, I would very much appreciate it.



Solution 1:[1]

I fixed this by using the suggestion posted here and switching to turbine for all my flow testing needs.

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 Joozd