Issue
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
I fixed this by using the suggestion posted here and switching to turbine for all my flow testing needs.
Answered By - Joozd
Answer Checked By - Mary Flores (JavaFixing Volunteer)