I'm trying to write unit testing for my ViewModel but I don't know how to deal with LiveData functions.
Specifically I'm not able to validate all the values that receive the LiveData Observer.
Regarding I have a Flow Use case that emit values and then is pased as a LiveData, what is the best approach to test operation function?
In the code below you can find that I'm only able to read the value "endLoading", but I want to check all the values: "startLoading", "Hello Dummy $input", "endLoading"
MainViewModel.kt
class MainViewModel(val useCase: DummyUseCase = DummyUseCase()): ViewModel() {
fun operation(value: Int): LiveData<String> = useCase.invoke(value)
.transform { response ->
emit(response)
}.onStart {
emit("startLoading")
}.catch {
emit("ERROR")
}.onCompletion {
emit("endLoading")
}.asLiveData(viewModelScope.coroutineContext)
}
MainViewModelTest.kt
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.Observer
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertNotNull
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ExperimentalCoroutinesApi
class MainViewModelTest {
//region Setup
@get:Rule
val rule = InstantTaskExecutorRule()
private val testDispatcher = TestCoroutineDispatcher()
@MockK private lateinit var stateObserver: Observer<String>
@MockK private lateinit var useCase: DummyUseCase
private lateinit var viewModel: MainViewModel
@Before
fun setup() {
MockKAnnotations.init(this, relaxUnitFun = true)
Dispatchers.setMain(testDispatcher)
viewModel = MainViewModel(useCase)
}
@After
fun teardown() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
//endregion
@Test // AAA testing
fun `when my flow succeeds, return a state String`() {
runBlocking {
//Arrange
val input = 10
coEvery { useCase.invoke(input) }.returns(flow {
emit("Hello Dummy $input")
})
//Act
val actual = viewModel.operation(input).apply {
observeForever(stateObserver)
}
//Assert
// I want to assert here every value received in the observer of the "actual" LiveData
// How? :(
assertNotNull(actual.value) // is always "endLoading"
}
}
}