I have a function in ViewModel with 2 states, first state is always LOADING, second state depends on result of api or db interactions.
This is the function
fun getPostWithSuspend() {
myCoroutineScope.launch {
// Set current state to LOADING
_postStateWithSuspend.value = ViewState(LOADING)
val result = postsUseCase.getPosts()
// Check and assign result to UI
val resultViewState = if (result.status == SUCCESS) {
ViewState(SUCCESS, data = result.data?.get(0)?.title)
} else {
ViewState(ERROR, error = result.error)
}
_postStateWithSuspend.value = resultViewState
}
}
And no error, test works fine for checking final result of ERROR or SUCCESS
@Test
fun `Given DataResult Error returned from useCase, should result error`() =
testCoroutineRule.runBlockingTest {
// GIVEN
coEvery {
useCase.getPosts()
} returns DataResult.Error(Exception("Network error occurred."))
// WHEN
viewModel.getPostWithSuspend()
// THEN
val expected = viewModel.postStateWithSuspend.getOrAwaitMultipleValues(dataCount = 2)
// Truth.assertThat("Network error occurred.").isEqualTo(expected?.error?.message)
// Truth.assertThat(expected?.error).isInstanceOf(Exception::class.java)
coVerify(atMost = 1) { useCase.getPosts() }
}
But i couldn't find a way to test whether LOADING
state has occurred or not, so i modified existing extension function to
fun <T> LiveData<T>.getOrAwaitMultipleValues(
time: Long = 2,
dataCount: Int = 1,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): List<T?> {
val data = mutableListOf<T?>()
val latch = CountDownLatch(dataCount)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data.add(o)
latch.countDown()
[email protected](this)
}
}
this.observeForever(observer)
afterObserve.invoke()
// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
this.removeObserver(observer)
throw TimeoutException("LiveData value was never set.")
}
@Suppress("UNCHECKED_CAST")
return data.toList()
}
To add data to a list when LiveData changes and store states in that list but
it never returns LOADING state because it happens before observe starts. Is there a way to test multiple values of LiveData
?
In this codelab, you use the JUnit framework to write unit tests. To use the framework, you need to add it as a dependency in your app module's build. gradle file. You can now use this library in your app's source code, and Android studio will help to add it to the generated Application Package File (APK) file.
ViewModel : Provides data to the UI and acts as a communication center between the Repository and the UI. Hides the backend from the UI. ViewModel instances survive device configuration changes. LiveData : A data holder class that follows the observer pattern, which means that it can be observed.
First, the LiveData should be observed in order to work properly. You can find a utility class at the end of the section to observe your LiveData from test class. Secondly we should add InstantExecutorRule test rule. InstantExecutorRule is a JUnit rule and it comes with androidx.
You have to change the value of the live data instead of just adding an item to the already set list. This will take your existing list then add your new item and then set the value of the live data so that the observe will be called.
inline fun <reified T > LiveData<T>.captureValues(): List<T?> {
val mockObserver = mockk<Observer<T>>()
val list = mutableListOf<T?>()
every { mockObserver.onChanged(captureNullable(list))} just runs
this.observeForever(mockObserver)
return list
}
I assume you are using mockk library
First you need to create observer object
val observer = mockk<Observer<ViewState<YourObject>>> { every { onChanged(any()) } just Runs }
Observe your livedata using previous observer object
viewModel.postStateWithSuspend.observeForever(observer)
Call your getPostWithSuspend() function
viewModel.getPostWithSuspend()
Verify it
verifySequence {
observer.onChanged(yourExpectedValue1)
observer.onChanged(yourExpectedValue2)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With