Test Driven Development in Android using MVVM, Rxjava and LiveData

Teo Boon Keat
7 min readNov 10, 2018

In this article, we will be building a small Android app using:

  • MVVM Architecture
  • LiveData
  • Rxjava’s Observable

Objectives

We will be covering the use of Mockito for writing our unit tests. We will not be writing any implementation code. We will be attempting true-blue Test-Driven-Approach.

In addition, these are the points that should be covered at the end of this article.

  • Creating mocks for your interface dependencies.
  • Defining the behaviors of these mocks.
  • Asserting the value of live data.
  • Verifying that a live data will update an observer when necessary.

This article brings you steps by steps from a new project. As with all great cooking shows, you can get the finished code at https://github.com/SPHTech/TestDrivenMVVM as well.

Requirements

Suppose today we were given a requirement, displaying a piece of data to a text view in an activity.

The piece of data could be from anywhere. E.g. shared preferences, database, sd card. Well, the job of implementing the data storage was given to another team. We were told to treat it as if it is there, but of course, it isn’t. We were only given a DataRepository interface.

interface DataRepository {
fun fetchData(): Observable<String>
}

Dream Up Some Architecture

So if we chose an MVVM approach, our solution would look like that.

Typical MVVM structured Android app

We shall have the text view (inside Activity or Fragment) observe a LiveData for changes. The LiveData will be located in our view model. Our solution should have a function to get data from DataRepository and update the LiveData.

Start Turning Dreams into Reality

We will focus on interaction between the view model, live data and our model (DataRepository).

Start a new Android project. Since we are using Rx observable and LiveData, add the dependencies to your app’s build.gradle

implementation "android.arch.lifecycle:extensions:1.1.1"
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'

Create a view model class with the live data and the function.

class MyViewModel {

val testLiveData = MutableLiveData<String>()

fun getStuff(){
TODO("not implemented")
}
}

Note that we do not write any implementation for the getStuff()first.

Our view model will be taking data from DataRepository, therefore we create the interface DataRepository.

interface DataRepository {
fun fetchData(): Observable<String>
}

We modify our view model to take in any class that implements a DataRepository interface.

class MyViewModel(private val dataRepository: DataRepository) {    val testLiveData = MutableLiveData<String>()    fun getStuff(){
TODO("not implemented")
}
}

Setting Up Project For Test

Place a cursor on MyViewModel and hit Alt-Enter. Select “Create test…” and you should get a dialog to help you create your test.

Dialog to help create unit tests

I recommend selecting the options as shown in the above picture. Click “OK”

Test Type Selection

Since we will be writing test cases that do not require an emulator, select the “test” folder as shown. Select “OK”. If all goes well, Android studio creates a class called MyViewModelTest.kt in your app/src/test folder.

Test case class created

We will be using Mockito to mock the DataRepository. We do not want to create actual implementations of DataRepository. Therefore we will use a mock to control DataRepository’s behavior, which you will see how later. You can think of a mock as a class that implements an interface. Add the dependency.

testImplementation 'com.nhaarman:mockito-kotlin:1.5.0'

Time to write tests!

Writing Unit Test

We are writing tests for our view model functions. Let’s not worry about other things like:

  • Is the observer code over at the UI side going to work?
  • Is the DataRepository going to return me correct data?

Our function getStuff() gets data from DataRepository and updates the live data. We need to write tests to cover the case where DataRepository returns:

  • onNext()
  • onError()
  • onCompleted()

When DataRepository returns onNext, we update our live data.

Create a DataRepository mock.

class MyViewModelTest {    @Mock
lateinit var mockDataRepository: DataRepository

@Before
fun setUp() {
}
@After
fun tearDown() {
}
@Test
fun getStuff() {
}
}

Initializing all the mocks you have in your test class.

class MyViewModelTest {    @Mock
lateinit var mockDataRepository: DataRepository

@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
@After
fun tearDown() {
}
@Test
fun getStuff() {
}
}

With a mock, we control how DataRepository behaves. Writing test to say we should update live data when DataRepository returns something. Create the following test in your test class MyViewModelTest.kt

@Test
fun `Given DataRepository returns data, when getStuff() called, then update live data`() {
//Setting how up the mock behaves
whenever(mockDataRepository.fetchData())
.thenReturn(Observable.just("Data"))
//Fire the test method
val myViewModel = MyViewModel(mockDataRepository)
myViewModel.getStuff()
//Check that our live data is updated
Assert.assertEquals("Data", myViewModel.testLiveData.value)
}

If you were to run this, it will display a not implemented error.

This is because we have included the TODO in our function.

class MyViewModel {

val testLiveData = MutableLiveData<String>()

fun getStuff(){
TODO("not implemented")
}
}

Remove the TODO and observe another kind of failure.

The live data was not updated. As expected since there is no implementation code.

Adding Rules to Test

Because we are using Rxjava and Live data, for tests we need to include some rules. If not, things will look ok for now but when an implementor tries to create code to pass your tests, he will be in trouble. I will explain this in another article in detail. In short, even if correctly implemented, this test throw some weird errors.

For now and simplicity, just add these two rules first. You can read more about RxImmediateSchedulerRule here and InstantTaskExecutorRule here. Basically, it helps to run things synchronously instead of asynchronously.

@Rule
@JvmField
var testSchedulerRule = RxImmediateSchedulerRule()
@Rule
@JvmField
val ruleForLivaData = InstantTaskExecutorRule()

You need to create a class RxImmediateSchedulerRule.kt. You can just place it in your app/src/tests folder. You can copy the code from here too.

class RxImmediateSchedulerRule : TestRule {
override fun apply(base: Statement, description: Description?): Statement {
return object : Statement() {
@Throws(Throwable::class)
override fun evaluate() {
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
try {
base.evaluate()
} finally {
RxJavaPlugins.reset()
RxAndroidPlugins.reset()
}
}
}
}
}

Writing Test for Unhappy Path

Just now, we wrote a test for what you sometimes hear as the happy path. Now let’s consider what we are to do for the unhappy path or error path. So we say that we do not change our live data when we got an error.

First, we refactor the creation of myViewModel in our setup().

  • Create a lateinit var myViewModel: MyViewModel
  • Initialize it after you init the mocks
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
myViewModel = MyViewModel(mockDataRepository)
}
  • In your test, reference the new myViewModel instead of creating it.
//Fire the test method
myViewModel.getStuff()

You should be getting this.

For the new test, we could follow largely the previous one.

  • Setup how the mock behaves
whenever(mockDataRepository.fetchData()).thenReturn(Observable.error(Throwable()))
  • Write an initial value to live data.
  • Fire the method.
  • Assert our live data did not change.

If you were to write such a test case, it will pass. We design it to do nothing on error. It is a valid test case since it serves as a guard. Our test will fail if someone actually updates our live data on error.

Another Verification Method for Live Data

In some scenarios, it might be better to create a mock observer to observe the live data. Then we check whether this mock observer’s onChange(…) function was fired or not.

  • Create a mock observer, use the observer from arch.lifecycle and not io.reactivex
import android.arch.lifecycle.Observer@Mock
lateinit var mockLiveDataObserver: Observer<String>
  • Let it observe our live data
myViewModel.testLiveData.observeForever(mockLiveDataObserver)
  • Fire the method getStuff() as usual
  • Verify that onChange(...) was not called
verify(mockLiveDataObserver, times(0)).onChanged(any())

You should be getting something like this.

What’s Next

You can go ahead and write the implementation code to pass the failing test cases. More work ahead.

  • onError should have a way to alert the user
  • onComplete, we might want to signal some other process to run if we receive this.

We will be looking at how to write tests to make sure we alert an activity on an error in the next article. I hope this article helps you and please let me know if something isn’t working for you as you try this.

--

--