Effective LiveData and ViewModel Testing

Many has been written about Architecture Components and how to implement them, having one of the benefits improved testability. Ok, but how does it actually improve? Let’s have a look.

Josef Raska
AndroidPub
Published in
3 min readJul 30, 2018

--

Architecture Components were introduced on Google I/O 2017 and they meant significant improvement in Android development world. Best practices learned over years were transformed into libraries handling most common problems and opinionated guide was out.

View and ViewModel communicates via LiveData

One of the key thoughts of Architecture Components is Observer Pattern for updating Activity or Fragment to handle commonly experienced problems.

Important part here is View remains passive, keeping all the logic in ViewModel, also dispatching user actions immediately to ViewModel. Complexity to verify by test should remain in ViewModel and if possible, we should avoid android.* dependencies to be able to test with pure JUnit test. This is important to achieve Effective ViewModel Test.

Effective ViewModel Test

We could discuss for ages, which test is effective and worth to write, but lets now set some goals we want our tests to achieve:

  1. Very fast test execution
  2. Modelling real use case of ViewModel under test
  3. Test is easy to write and maintain

Resolving first requirement means keeping the test in JUnit only, without using Robolectric or instrumentation test. This is possible to achieve and potentially worth the effort.

Discussing requirement 2. brings us to the point why following Architecture Components makes sense in case of improved testing. Separation of components makes easy to replace Activity by something else. Test can behave as Activity whilst keeping the test modelling real use case of interaction with ViewModel.

Now let’s discuss the last requirement of easy writing and maintenance.

Writing effective ViewModel test

During the first presentation of LiveData and ViewModel components, expected question was soon raised: “Is this RxJava?”. It is not, but there are clearly some common patterns.

Having this in mind, together with the fact that RxJava is brilliant and very well tested library, we can take inspiration from testing approach there. Each type has a method test() returning TestObserver or TestSubscriberto perform fluent test assertions.

We can implement our own TestObserverfor LiveDatawith required assert methods and since we now wield tools like Kotlin extension methods, we can achieve same comfort as RxJava to have test() method on LiveData directly. Code using livedata-testing library will look like this:

viewModel.counterLiveData()
.test()
.assertHasValue()
.assertValue { it > 3 }
.assertValue(4)
.assertNever { it > 4 }
viewModel.plusButtonClicked() // internally increments counterviewModel.counterLiveData()
.test()
.assertValue(5)
.assertNever { it > 5}
...

Implementation of this uses simple extension method to subscribe to LiveData and return TestObserver having introduced assertion methods.

fun <T> LiveData<T>.test(): TestObserver<T> {  
return TestObserver.test(this)
}

TestObserver is Java class, so you can use this approach even if your project is using only Java without Kotlin. The only difference would be calling static method:

TestObserver.test(viewModel.counterLiveData())
.assertHasValue()
...;

Please keep in mind that all tests using LiveData must currently include InstantTaskExecutorRule from android.arch.core:core-testing artifact.

Is writing tests like this effective?

So far, writing tests like this fulfilled the goals set:

  1. Test is executed fast by using pure JUnit test.
  2. Test is modelling Activity, therefore the use case is the same as in production Android code.

3. Using TestObserver and possibly Kotlin extension methods in our project and properly implementing the Architecture Components ideas, our test should be fast to write and potentially easy to maintain.

Checking livedata-testing library might give you more ideas and examples how to write tests like this. If you decide to try it or if you use different testing approach, let me know what you think. Happy testing!

--

--

Josef Raska
AndroidPub

Key to being a good engineer is largely to use your judgment and avoid problems that would require a good engineer to solve them.