Adding Unit Tests To An Android Project That Uses Coroutines And LiveData

Carlos Daniel
4 min readJun 17, 2020
Photo by timJ on Unsplash

If our Android app is using Kotlin Coroutines and LiveData, we need to take into consideration the way all unit testing process is made. To show its implementation on a actual project, we are going to continue using the NASA Rover Photos app, the project we have been working with during previous articles.

1. Step by Step to Bottom Navigation with Jetpack’s Navigation Component and multiple nav graphs
2. Implementing an Android app with Jetpack, MVVM + UI State Manage (and some other interesting stuff)
3. Adding Espresso UI tests with Idling Resources

NASA Rover Photos App

Adding The Required Dependencies

The first step is to set up our dependencies. Then we add Mockito’s (to mock objects), Arch Core’s (to work with LiveData) and Coroutines Test (to use dispatchers properly):

As we are using Mockito, remember to add the file resources/mockito-extensions/org.mockito.plugins.MockMaker with the content mock-maker-inline in order to let Mockito “extend” all final Kotlin classes.

Creating A Test Coroutine Rule

This is a custom rule that allows us to set and reset the main dispatcher when running a unit test that uses coroutines. Also we can define there a method that returns a runBlockingTest, which scopes and makes the test run on the main thread. As this is an experimental development of the Coroutines Api, the test should be marked as experimental (@ExperimentalCoroutinesApi). Remember that we create this rule in order to use it easily in different tests as well as different classes.

Creating The Test

When testing the ViewModels, as we can see, we use the InstantTaskExecutorRule to indicate that we are working with LiveData and forces to execute the LiveData in the same test thread. Also we are using the custom rule we just created TestCoroutineRule. The rest is pure unit testing, creating all required mocks (the repository and the observer for this case) and logic verifications. To highlight, when a coroutine is used the test must be executed within the runBlockingTest block on its proper scope.

For the record, I like to use a mix between Mockito’s mock library verification and classic jUnit’s asserts; and to improve readability and avoid writing `when` instructions, I define an alias (whenever) to the import as well. It’s just the way I fell more confortable with, not the best/unique.

import org.mockito.Mockito.`when` as whenever

Testing The Repository

There are some people who like to add unit tests to the Repository class, and there are some who believe that it’s not necessary due to the nature of the repository just being a repo/container class. Anyway, we can assure that we are getting the expected results in the Repository class using Square’s MockWebServer library can be of help with the idea to mock an API response using a local JSON file with the same structure of the respective response.

To achieve this, after including the dependency (remember the snippet above in the first section) we create an abstract class that all our tests are going to implement, in order to control mock server lifecycle and having all its logic centralized, as well as defining its own test service api (at this point we are not using any DI framework then it’s easier to define this test api service and use it within the tests, when using a DI framework such as Dagger2, the proper way is to create a Test Api Module within the tests package with the mocked test).

Then, our repository unit test class would look like this:

The most important thing here is the use of native coroutine’s runBlocking block instead of runBlockingTest. Why doesn’t it work with runBlockingTest? It’s still not explained at all, but in the thread of this issue are all the stack of solutions/workarounds. The rest is pure mockwebserver + mockito + asserts.

If you have been following the project since the beginning, you may find that some minor changes were made to the code, in order to proper run the test and to fulfill different escenarios, then all of them are in the current branch of the repo (unit_tests).

Remember to follow the Github’s repo changes in which I’m going to be adding some new stuff.

--

--

Carlos Daniel

Android & Flutter Developer. GDE for Android & Mobile Engineer.