You made it to the second part of this article where I try to tell about actual testing patters. But before I thought this conference talk by Zac Sweers would be a perfect preface that’ll help to understand what are you writing tests for.

In order to clearly understand what kind of tests you need for every component of MVVM imagine them as black boxes.

In the Model component the business logic is a black box. Main principle here is to verify that valid input data is processed correctly according to the business rules into expected output data. All invalid or unexpected input data is also handled correctly.

In the ViewModel component the logic to prepare data for the UI is a black box. Often the input of the ViewModel is the output of Model so the main principle here is to verify that any possible data the model produces is converted and sent as output in a format so thee View can display it. If you look at this from the code perspective: any interaction with a ViewModel should produce expected LiveData event.

In the View component the black box is the UI logic, for example displaying text in a TextView or showing/hiding a View, etc. The input to the View is the output of the ViewModel and the output is the state of the UI (I like to imagine it as a screenshot of app screen).

It’s very important to understand borders between those components. When test one component you don’t need to have other components. For example, when you test the Model you don’t need to have any mentions of ViewModel and especially View in the test. Another example is ViewModel where you might think you need Model but you actually just need different outputs the model can product. To achieve that mocks come very useful since they just simulate behaviour of a real model which you can control.

Model and ViewModel tests are usually unit tests or JVM tests. In order to run them you don’t need any of the parts of Android framework. Because of that such tests are very fast and reliable. Try to cover as much as you can using those types of tests.

View tests are the different. The can’t be tested without Android framework. What it means is they can’t be run without emulator or real device. Such tests are slow, less reliable and very dependent on the environment they’re run in.

View tests are usually instrumented tests.

Note: in Android Studio project instrumented tests live in androidTest folder, test is for JVM tests

Now, assuming you know what are you testing, let’s start with couple tools of choice.

Truth: nice API for assertions and very readable failure messages

2. Mockito

3. Mockito-Kotlin which provides a nice API for using Mockito in Kotlin (

First component to test (and the one this article will mostly be focused on) is going to be ViewModel . As I mentioned before, we’re going to interact with ViewModel and assert produced events that are sent via LiveData .

So here’re 3 steps every test case would contain of:

Set up ViewModel ’s dependencies, i.e. mock some Models that ViewModel will use. Interact with ViewModel , i.e. call its public methods (the ones that are listed in Contract ). Observe LiveData s and verify the data that comes with LiveData ’s events

First, create a test class in test directory of your project. Note: you would always need to add InstantTaskExecutorRule because some Android Architecture Components execute tasks in background but we don’t want that in our tests. Then mock all dependencies ViewModel uses (they are passes as ViewModelFactory’s constructor arguments).

class ProductListVIewModelTest {



@Rule

@JvmField

var instantExecutorRule = InstantTaskExecutorRule()



private val dataProvider: DataProvider = mock()

private val cart: Cart = mock()

}

Second, interaction with ViewModel . I am going to test the part that is responsible for adding product to the cart when a product in the list is clicked and updating checkout button with new cart item quantity and cart subtotal.

This 🙂

As you could already guessed two logical pieces here to test are:

1) Product is added to the cart.

2) Event to update view state is sent

override fun onProductClicked(productId: Long) {

backgroundExecutor.execute {

val product = dataProvider.fetchProductWithId(productId)

product?.let {

cart.addProduct(product)

_checkoutButtonPayloadLiveData

.postValue(cart.toCheckoutButtonPayload())

}

}

}

However as you can see onProductClicked method does some additional actions here. Since we only need to worry about addition to the cart and _checkoutButtonPayloadLiveData ‘s event and payload we don’t care about other things. dataProvider.fetchProductWithId is used to load product from the database but assuming there’re tests that fully cover database logic and give us confidence that fetching variant works we can mock it. toCheckoutButtonPayload() is an extension function of Cart class that call its two methods cartSize() and cartSubtotal() and creates a simple view data class that View knows how to present. Thus we also need to mock those two methods.

data class CheckoutButtonPayload(

val quantity: Int,

val subtotal: String

)



fun Cart.toCheckoutButtonPayload(): CheckoutButtonPayload {

return CheckoutButtonPayload(cartSize(), cartSubtotal())

}

The test case would look something like that:

@Test

fun `adding product to a cart returns one item quantity and subtotal`() {

val product: Product = mock()



whenever(dataProvider.fetchProductWithId(any())) doReturn product

whenever(cart.cartSize()) doReturn 1

whenever(cart.cartSubtotal()) doReturn "$5.00"

}

Then we need to pass those dependencies into ViewModelFactory to create a properly configured for that test case ViewModel and create mock Observer that is needed for verifying LiveData ’s events.

@Test

fun `adding product to a cart returns one item quantity and subtotal`() {

val product: Product = mock()



whenever(dataProvider.fetchProductWithId(any())) doReturn product

whenever(cart.cartSize()) doReturn 1

whenever(cart.cartSubtotal()) doReturn "$5.00"



val viewModel = ProductListViewModel.ProductListViewModelFactory(

dataProvider,

cart

).create(ProductListViewModel::class.java)



val mockObserver: Observer<CheckoutButtonPayload> = mock()

viewModel.checkoutButtonPayloadLiveData.observeForever(mockObserver)



}

And finally we need to actually call onProductClicked method explicitly and verify that behaviour we expect to happen are actually happening. Mockito’s verify(mock: T) is used to verify the behaviour happens just once.

The complete test case would look like this:

@Test

fun `adding product to a cart returns one item quantity and subtotal`() {

val product: Product = mock()



whenever(dataProvider.fetchProductWithId(any())) doReturn product

whenever(cart.cartSize()) doReturn 1

whenever(cart.cartSubtotal()) doReturn "$5.00"



val viewModel = ProductListViewModel.ProductListViewModelFactory(

dataProvider,

cart

).create(ProductListViewModel::class.java)



val mockObserver: Observer<CheckoutButtonPayload> = mock()

viewModel.checkoutButtonPayloadLiveData.observeForever(mockObserver)



viewModel.onProductClicked(1L)



verify(cart).addProduct(any())

verify(mockObserver).onChanged(any())

}

Note the we don’t need to care about which exact product was clicked so we could pass any product ID as an arguments. We just verify that the interaction with ViewModel calls addProduct method once and observer registers one event sent by checkoutButtonPayloadLiveData .

Additionally we could assert correctness of the data too. Just change last two lines to:

verify(cart).addProduct(product)

verify(mockObserver).onChanged(check {

assertThat(it.quantity).isEqualTo(1)

assertThat(it.subtotal).isEqualTo("$5.00")

})

Note: first line performs one assertion on the received argument. Using check function you could have multiple assertion on the received argument.

🎉the test passed (super fast)

As a second example we could write another test case here is to verify that clicking on product twice, i.e. calling onProductClicked twice would also perform two interactions with the cart and send two LiveData events.

@Test

fun `checkout button is updated on every product addition`() {

val product: Product = mock()

whenever(dataProvider.fetchProductWithId(any())) doReturn product



val viewModel = ProductListViewModel.ProductListViewModelFactory(

dataProvider,

cart

).create(ProductListViewModel::class.java)



val mockObserver: Observer<CheckoutButtonPayload> = mock()

viewModel.checkoutButtonPayloadLiveData.observeForever(mockObserver)



viewModel.onProductClicked(1L)

viewModel.onProductClicked(1L) verify(cart, never()).deleteProduct(any())

verify(cart, times(2)).addProduct(any())

verify(mockObserver, times(2)).onChanged(any())

}

Using verify(mock: T, mode: VerificationMode) we could verify that behaviour happens at least once /exact number of times / never. As a bonus here we could also test that clicking on product never deletes anything from the cart. It seems quite obvious but what if!

To extend previous example we can change the test to verify that clicking different products items in the list adds different products in the cart. For this case we would use argument captors which will keep track of all the interactions with the mock. In this case clicking a product with id 1L will add first product in the cart, and then clicking a product with id 2L will add second product in the cart. Then we just verify captured interactions in the order they happened.

@Test

fun `checkout button is updated on every product addition`() {

val productOne: Product = mock()

val productTwo: Product = mock()



whenever(dataProvider.fetchProductWithId(1L)) doReturn productOne

whenever(dataProvider.fetchProductWithId(2L)) doReturn productTwo



val viewModel = ProductListViewModel.ProductListViewModelFactory(

dataProvider,

cart

).create(ProductListViewModel::class.java)



val mockObserver: Observer<CheckoutButtonPayload> = mock()

viewModel.checkoutButtonPayloadLiveData.observeForever(mockObserver)



viewModel.onProductClicked(1L)

viewModel.onProductClicked(2L)



verify(mockObserver, times(2)).onChanged(any())



argumentCaptor<Product> {

verify(cart, atLeastOnce()).addProduct(capture())



assertThat(allValues).hasSize(2)

assertThat(firstValue).isEqualTo(productOne)

assertThat(secondValue).isEqualTo(productTwo)

}

}

Those two and a half examples of tests should work for 99% of cases when you write tests for your own view models if you implemented them similarly to what you see in this article (or in Google’s ViewModel tutorials).

Next component to test is View . I’m less opinionated about perfect tool for testing here so I’ll just list couple and give some tips on how to make View testing easier.

The general idea is to implement a View so it can have a deterministic set of visual states including the default state. Just view inflation (without any data sent to it) should look decent enough to present to a user without feeling shame. Individual sub-views could have their own states but changing those states should not change the state of a parent view. They can however affect other sub-view. My explanation might not be perfect but if you can strip the sub-view from the parent view and it should still work, look nice and be testable. Just stick to a composition principle and you’ll be ok. Next , tools:

Espresso

TL;DR Good but not the best. Very easy to imitate user interactions (button clicks, text input, etc.) but harder to see the test output. In other words, you can tell based on assertion whether a particular view is visible or not, but if you want to see how the view looks overall you might use something different, like screenshots.

2. Screenshot testing libraries

Disclaimer: I haven’t tried facebook library but the description looks promising.

At Shopify we use in-house built screenshot library which is has a quite similar idea. It combines espresso actions like view clicks and text inputs and screenshot asserting functionality. When you write a test, you record a baseline screenshot image which is then saved somewhere in the androidTest/assets folder. Next time you run the test case it generates new image and compares it with a baseline pixel by pixel. Non-identical images fail test. The downside is that screenshot tests are very sensitive to the environment they run at so they could start failing if you change size of the emulator or Android version. It could also fail if the OS has different GPU drivers so SVGs are rendered differently. Or tens of other wild random reason.

Last component to test in this article is Model . I intentionally left it until the end because this is the heaviest one. However, I won’t cover how write unit tests for model layer for two reasons: there’s plenty of great articles online ( Android testing) and I don’t want to embarrass myself feeling how little I know about it 😄). So good luck with that!

This is the end of the article. Thanks for spending your time reading it and let me know if I’m wrong somewhere or if there’s something that could do differently or better. I’m open to your feedback 🙌🏻