19th May 2024
Testing flows can get challenging. Specifcially, when we to test each item that is sent through the flow. Here this amazing library called Tubrine comes to rescue. Is is developed by CashApp.
So here is how it works.
Suppose you have this ProfileViewModel
which have a function that loads the user. It emits the state using a data class called ProfileState
:
data class ProfileState(
val profile: Profile? = null,
val isLoading: Boolean = false,
val errorMessage: String? = null
)
And the function that loads this profile looks like this:
class ProfileViewModel(private val repo: Repository){
private val _state = MutableStateFlow(ProfileState())
val state = _state.asStateFlow()
//...
fun loadProfile(){
viewmodelScope.launch{
_state.update{it.copy(isLoading = true)}
val profile = repo.getProfile("someID);
_state.update{
it.copy(
profile = result.getOrNull(),
isLoading = false,
errorMessage = profille.exceptionOrNull()?.message
)
}
}
}
}
It just loads the data and updated the state flow.
As usual we will create a test class in the Unit Test Resource directory and call it ProfileViewModelTest
class ProfileViewModelTest{
private lateinit var viewmodel: ProfileViewModel
private lateinit var repository: Repo
@Before
fun setup(){
repository = FakeRepo()
viewmodel = ProfileViewModel(repository)
val testDispatcher: TestDispatcher = StandardTestDispatcher() //why
Dispatchers.setMain(testDispatcher) //why
}
//question why we use runTest?
@Test
fun `Test loading state update`() = runTest{
//this is how flow testing starts
viewmodel.state.test{
//first we make sure that it shows correct state
val emission1 = awaitItem()
//above will suspend the execution until an item in viewmodel.state is observer
//once program proceeds we are sure that emission1 will have the first emission
//so we test that first
assertThat(emission1.isLoading).isFalse()
//now we load the profile and see if the state updates accordingly
viewmodel.loadProfile()
//now we get the next Item using the same function
val emission2 = awaitItem()
//test that
assertThat(emission2.isLoading).isTrue() //it should be true
//final emission
val emission3 = awaitItem()
assertThat(emission3.isLoading).isFalse()
}
}
}
NOTE:
assertThat
,isTrue
etc comes from AssertK Library but you can use any library for validation
NOTE:
TestDispatcher
andStanderdTestDispatcher
comes fromkotlin-coroutine-test
library
FINAL NOTE: Will also explain in future all the whys in the code.
Cheers
PS: The follow repos could be helpful: