I have ViewModel which exposes flow to fragment. I am calling API from ViewModel's init which emits different states. I am not able to write unit test to check all the emitted states.
My ViewModel
class FooViewModel constructor( fooProvider : FooProvider){
private val _uiState = MutableSharedFlow<UiState>(replay = 1)
// Used in fragment to collect ui states.
val uiState = _uiState.asSharedFlow()
init{
_uiState.emit(FetchingFoo)
viewModelScope.runCatching {
// Fetch shareable link from server [users.sharedInvites.list].
fooProvider.fetchFoo().await()
}.fold(
onSuccess = {
_uiState.emit(FoundFoo)
},
onFailure = {
_uiState.emit(EmptyFoo)
}
)
}
sealed class UiState {
object FetchingFoo : UiState()
object FoundFoo : UiState()
object EmptyFoo : UiState()
}
}
Now I want to test this ViewModel to check if all the states are being emitted.
My Test: Note I am using turbine library.
class FooViewModelTest{
@Mock
private lateinit var fooProvider : FooProvider
@Test
fun testFooFetch() = runTest {
whenever(fooProvider.fetchFoo()).thenReturn(// Expected API response)
val fooViewModel = FooViewModel(fooProvider)
// Here lies the problem. as we create fooViewModel object API is called.
// before reaching test block.
fooViewModel.uiState.test{
// This condition fails as fooViewModel.uiState is now at FoundFoo.
assertEquals(FetchingFoo, awaitItem())
assertEquals(FoundFoo, awaitItem())
}
}
}
How to delay init
till inside on .test{}
block.
Tried creating ViewModel object by Lazy{}
but not working.
It is not very pragmatic to "delay" emissions for sake of testing, this may produce flakey tests.
This is more of a coding issue - the right question should be "Does this logic belong in the class initialisation. The fact that it is more difficult to test should give you hints that it is less than ideal.
A better solution would be to use a
StateFlow
which is lazily initialised something like (some code assumed for sake of testing) :Then testing could be something like :
addotional test dependencies :