Ensure your ViewModel does what it’s supposed to do.

Android Architecture Component was launched in Google IO 2017. One of the key thoughts of Architecture Components is Observer Pattern for updating Activity and Fragment. We use LiveData which resides in ViewModel and observed in Activity/Fragment. It emits the data whenever there is a change and updates the UI.

We use ViewModel to store and manage UI-related data and LiveData to pass the same data to Activity/Fragments. As a result, most of the logic lives inside the view model. Using ViewModel separates the business login from UI-related logic. Therefore, it’s especially important to test our view model thoroughly.

A good architecture separates concerns into components and makes Unit testing become easier.

In this article we will talk about effective way of testing your ViewModel.

For the reference this is how our ViewModel looks like which we are going to test.

I have used Dagger to provide dependencies, RxJava for API call , room DB for cache, threading and LiveData to notify view.

Setting up HomeViewModelTest

First we need to set rule to use Architecture Components

@Rule

val instantExecutorRule = InstantTaskExecutorRule()

InstantTaskExecutorRule comes from the androidx.arch.core:core-testing library.

A JUnit Test Rule that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously.

@RunWith(MockitoJUnitRunner.class)

class HomeViewModelTest {



private lateinit var viewModel: HomeViewModel @Mock

private lateinit var homeRepo: HomeRepository @Mock

private var observer:Observer<Resource<List<HomeItem>>> @Captor

private lateinit var argumentCaptor: ArgumentCaptor<Resource<List<HomeItem>>> @get:Rule

public val instantExecutorRule = InstantTaskExecutorRule()



@Before

fun setupMyTripViewModel() {

RxJavaPlugins.setIoSchedulerHandler{Schedulers.trampoline()}

}

}

@Mock creates mock object of given class or interface.

@Captor will create the object of ArgumentCaptor of Resource<List<HomeItem>>. ArgumentCaptor is used to capture argument values for further assertions.

To use @Mock and @Captor annotations, we need to run out test with MockitoJUnitRunner.

RxJavaPlugins.setIoSchedulerHandler{Schedulers.trampoline()}

Schedulers.trampoline() method creates and returns a Scheduler that queues work on the current thread to be executed. So code will run synchronously wherever we use Schedulers.io().

We have utility class HomeItemFatory, through which we will get the mocked list of HomeItem.

@Test

fun loadHomeItemTriggersLoadingState(){ Mockito.`when`(homeRepository.getHomeCache())

.thenReturn(Single.just(HomeItemFatory.getListOf(1)))

Mockito.`when`(homeRepository.getHomeItems())

.thenReturn(Single.just(HomeItemFatory.getListOf(2))) Mockito.verify(observer, Mockito.times(3))

.onChanged(argumentCaptor.capture()) viewModel.homeItem.observeForever(observer)

viewModel.loadHomeItem() val values = arugmentCaptor.getAllValues();

Assert.assertEquals(Status.LOADING,values.get(0).status) }

Mockito.verify verifies certain behavior happened at least once / exact number of times / never.

times(3) allows verifying exact number of invocations.

argumentCaptor.capture() use it to capture the argument.

argumentCaptor.getAllValues() returns all captured values.

Here we are using argumentCaptor to capture arguments values passed in onChanged method of Observer.

Our livedata will fire 3 events, we’ll see later in this post what events it will be.

This test is checking that when we call the loadHomeItem() method, our livedata will fire the first event with Status.LOADING.

We started observing using mocked instance.

viewModel.homeItem.observeForever(observer)

If our livedata doesn’t have an observer, then onChanged events will not be called. This observer instance allows us to add a observer to our livedata so that we can capture arguments get passed into onChange.

values.get(0) will give return the arguments value which was captured on firest invocation.

Another approach of testing livedata is directly get value instead of setting the observer.

val resource = viewModel.homeItem.value

Assert.assertEquals(Status.LOADING,resource.status)

So the question is why do we need observer?

By observing LiveData means that updating the view the whenever it emits the data. So it will trigger event multiple times depending on our code.

How do we test the triggered events ?

This is where Observer comes into the picture when we’re testing LiveData. Event will be triggered everytime it emits.

When everything went good

Now let’s check our ViewModel again, what we are doing is, on calling loadHomeItem()

update the LiveData with LOADING state then update the LiveData with SUCCESS state and cache data then after successfully getting the remote data from API, we’re updating the LiveData with SUCCESS state and remote data. And if we get any error then we update the LiveData with ERROR state and message.

With that said we will be verifying 3 values of LiveData.

@Test

fun loadHomeItem_triggers_3_state_success(){



//mocking cache data

val cacheList =HomeItemFatory.getListOf(1)

Mockito.`when`(homeRepository.getHomeCache())

.thenReturn(Single.just(cacheList))



//mocking api data

val apiList = HomeItemFatory.getListOf(2)

Mockito.`when`(homeRepository.getHomeItems())

.thenReturn(Single.just(apiList))





Mockito.verify(observer, Mockito.times(3))

.onChanged(argumentCaptor.capture()) viewModel.homeItem.observeForever(observer)

viewModel.loadHomeItem() val values = arugmentCaptor.getAllValues();

Assert.assertEquals(Status.LOADING,values.get(0).status)



Assert.assertEquals(Status.SUCCESS, values.get(1).status)

Assert.assertEquals(cacheList, values.get(1).data)



Assert.assertEquals(Status.SUCCESS, values.get(2).status)

Assert.assertEquals(cacheList, values.get(2).data)



}

times(3) will verify the 3 invocation else it will throw error. We need to verify the livedata trigger 3 events.

Then we will assert the all 3 eventvalues.