Android clean architecture with ViewModel, UseCases and Repositories: Part 2 - Unit Testing

Antoni Castejón García
ProAndroidDev
Published in
6 min readFeb 20, 2018

--

Welcome back to the second part of a set of articles about clean architecture using ViewModels and LiveData for Android in Kotlin. If you didn’t read the first part I highly reccommend you to do it, because in this second part I am going to talk about applying unit testing to the architecture explained there.

Okay so… let’s start! For our unit testing I’m going to use JUnit and Mockito. Also, we need the testing library for android architecture components.

Before reading this article you have to know that I assume that you are aware about the basics of unit testing on Android, Mockito and RxJava2. I am going to focus in explaining how to test specifically the architecture.

Let’s continue! We need to add this dependencies to our app build.gradle file:

testImplementation 'junit:junit:4.12'
testImplementation 'org.mockito:mockito-core:2.10.0'
testImplementation 'android.arch.core:core-testing:1.1.0'

Do you remember how the architecture is? It is a MVVM architecture with:

  • The View (Fragments, Activities…)
  • ViewModels with a LiveData member that holds…
  • … the state of the View or the Model.
  • Use Cases
  • Repositories

The unit testing will focus on testing the ViewModels and the Use Cases. Also, you can add unit testing to your common utils functions. Maybe you are asking yourself:

“Whhattt?! Wait! Why not to test also the Views and the Repositories?!”

Before you stop reading it has an answer. The repositories act as in-memory domain object collection (I don’t invent that, Martin Fowler does). Normally, these objects are retrieved from third party libraries (Retrofit, in this case) that we have to assume that they pass his own unit tests and they work fine. Also, it has no sense to test a request to an API because we don’t know if that request will give us an error, a 200 code response or whatever. Another case is a repository that retrieves/saves data to a local database. In that case we have another problem: The unit test is not executed in a real device so there is no local database, then making a unit test is impossible.

About the unit testing on Views: Following a clean architecture all the bussiness logic have to be in a deeper layer than the View one. That means that the view must no have, for instance, any conditional that has to be tested. Then… don’t you think that has no sense to test the click on a Button, the scroll on a RecyclerView or something like that? This is for instrumentation tests and that is it another topic.

“Okay, okay, now I understand. So please, continue.”

Good to hear that :)

Before to start the next part I would like to reccomend the Niek Haarman mockito-kotlin library from where I get the idea of the MockitoUtils file that you can find in my repository. Nowadays is basically an util file with this two methods:

inline fun <reified T> mock() = Mockito.mock(T::class.java)inline fun <T> whenever(methodCall: T) : OngoingStubbing<T> =
Mockito.`when`(methodCall)

We will look in the following paragraphs the purpose of them.

Use Cases unit tests

To test the Use Cases we have to mock the repository interface used on it. We mock it to make it to give a response we need for this specific unit test. For instance, we want to verify that our Use Case acts as we expect in case that repository returns a set of objects or an empty list, even an error.

This is how the unit tests of the CryptoListUseCases looks like:

The first lines of the test:

@Rule 
@JvmField
val rule = InstantTaskExecutorRule()

I need to add use rule to allow to android components used in our unit tests to be executed synchronously instead of using the background executor used normally.

val coinMarketCapRepository = mock<CoinMarketCapRepository>()                                                   val cryptoListUseCases by lazy {
CryptoListInteractor(coinMarketCapRepository)
}

Here, I have the mocked repository and a real instance of the CryptoListUseCases. Because of I am mocking the repository to make it to return the reponse the test needs, an instance of the Use Case is created in order to use its public methods that use the repository.

The CryptoListUseCases instance is created with a real instance of the repository on Application through dependency injection however, on unit tests, it is created calling his constructor using the mocked CoinMarketCapRepository.

The mock<>() function used is an inline one that calls the Mockito’s mock method but, in my opinion, more readable.

The first unit test looks like:

@Test                           
fun testCryptoListUseCases_getCryptoList_Completed() {
whenever(coinMarketCapRepository.getCryptoList(
anyInt(), anyInt()))
.thenReturn(Single.just(emptyList()))

cryptoListUseCases.getCryptoListBy(0)
.test()
.assertComplete()
}

If you have used Mockito before, maybe you are asking yourself why I changed the Mockito.when method to the inline whenever one from the MockitoUtils file. That’s because when is a reserved word in Kotlin so to use it in our tests we should write it this way: `when`(repo.somemethod()). Don’t you think that it is more readable this way?

In this unit test I am telling to the repository to returns a Single with an emptyList when anyone calls the getCryptoList method. Then, in the last lines of the method I assert that the TestObserver from RxJava2 is completed.

In the second unit test I want to know that everything is okay if the repository returns an error.

@Test
fun testCryptoListUseCases_getCryptoList_Error() {
val response = Throwable("Error response")
whenever(coinMarketCapRepository.getCryptoList(
anyInt(), anyInt()))
.thenReturn(Single.error(response))

cryptoListUseCases.getCryptoListBy(0)
.test()
.assertError(response)
}

Very similar to the first one, right?

And in the third unit test I want to verify that the response from the repository (an ArrayList of Crypto objects) is mapped properly to the view model (an ArrayList of CryptoViewModel objects).

@Test
fun testCryptoListUseCases_getCryptoList_response() {
val response = arrayListOf(cryptoPOJOmodel())
whenever(coinMarketCapRepository.getCryptoList(
anyInt(), anyInt()))
.thenReturn(Single.just(response))

val expectedList =
arrayListOf(cryptoViewModelFrom(cryptoPOJOmodel()))

cryptoListUseCases.getCryptoListBy(0)
.test()
.assertValue(expectedList)
}

This is a little more complex so we will look it in detail. The cryptoPojoModel() and the cryptoViewModelFrom(pojoModel) methods are util functions in the CryptoListMockObjects file present in the common package:

So, basically I am asserting that if I request a Crypto list to the CryptoListRepository, the client will receive the expectedList of CryptoViewModel objects.

ViewModels unit tests

The CryptoListViewModel unit test looks like:

Firstly, I create a real instance of the ViewModel but with different Rx Schedulers: in the application, it is constructed using the Schedulers.io() and the AndroidSchedulers.mainThread() but in unit tests it is constructed using Schedulers.trampoline().

Secondly, I mocked the CryptoListUseCases interface used on it, also I created a mock of an CryptoListState Observer to verify changes on the view state.

In every test the LiveData object is observed with the method observeForever and giving to it the mocked Observer. I am not using the observe method because I don’t want the Observer to be notified depending on a lifecycle that doesn’t exists, it is a unit test, I want the Observer to be notified at any time.

The Mockito’s ArgumentCaptor is used to capture every change on CryptoListState from ViewModel’s LiveData object that is observed. Lastly, we assert that the captured states are the expected ones.

In the first unit test we want to verify that the state is properly set after loading a list of one element.

@Test                           
fun testCryptoList_updateCryptoList_LoadOnePage() {
val response = arrayListOf(CryptoViewModel())
whenever(cryptoListUseCases.getCryptoListBy(anyInt()))
.thenReturn(Single.just(response))

viewmodel.stateLiveData.observeForever(observerState)

viewmodel.updateCryptoList()
val firstPage = 0
verify(cryptoListUseCases).getCryptoListBy(firstPage)

val argumentCaptor =
ArgumentCaptor.forClass(CryptoListState::class.java)
val expectedLoadingState =
LoadingState(firstPage, false, emptyList())
val expectedDefaultState =
DefaultState(firstPage+1, true, response)
argumentCaptor.run {
verify(observerState, times(3)).onChanged(capture())
val (initialState, loadingState, defaultState) = allValues
assertEquals(loadingState, expectedLoadingState)
assertEquals(defaultState, expectedDefaultState)
}
}

Maybe the trickiest part of the test is the ArgumentCaptor one. So I will explain line by line for this test:

verify(observerState, times(3)).onChanged(capture())

I verify that the method onChanged from the observerState is called 3 times. This method is called by the framework when the LiveData object changes and it receives the new value by parameter. I capture all these values.

val (initialState, loadingState, defaultState) = allValues

I use the getAllValues() method (allValues thanks to Kotlin) and we destructure the array returned (to learn about destructuring in Kotlin you can check this) in the three expected values. The values are sorted as received by the observer, so we can know which state is every value if everything goes as expected.

assertEquals(loadingState, expectedLoadingState)                                   
assertEquals(defaultState, expectedDefaultState)

Lastly, we assert that the received states we want to verify are the expected ones.

To finish the article, I would like to say that the other two test are equals than the first one. The second one checks if the received states doing a pagination are the correct ones and the third one checks if the the received states when the ViewModel receives an error are ok.

--

--