Unit-testing LiveData and other common observability problems

Jose Alcérreca
Android Developers
Published in
3 min readSep 2, 2019

--

Next time you’re scratching your head wondering why an innocent looking unit test with LiveDatas is failing, or staring at an empty screen that should show… something, you should remember that a LiveData that is not observed won’t emit updates. In this post you’ll learn some good practices to avoid this problem.

This is especially important when dealing with Transformations. If a LiveData is transformed, the result of the transformation must be observed. Otherwise the transformation will never be evaluated.

If a LiveData falls in a forest and no one is observing it… was it ever updated?

In this example, the value of the initial _liveData1 is transformed (mapped) to upper case whenever the initial value changes:

Let’s write a simple test for it:

This test fails because LiveData doesn’t do more work than needed. Reading liveData2.valuedoesn’t initiate the chain of dependent transformations because it’s not observed. Only a subscription through observe() does that.

The result of a Transformation is not computed if it’s not observed

Note that liveData1’s value can be read because it’s a MutableLiveData and no transformations need to be evaluated.

A simple (but not great) way to fix this issue would be to call observeForever() on the LiveDatas that we need to read:

The result of a Transformation is not computed if it’s not observed

However if we need to observe multiple LiveDatas this can fill our tests quickly with unimportant statements.

To fix this, Jetpack doesn’t provide a test helper yet, but we can make our own:

Also available in Java.

This function observes a LiveData until it receives a new value (via onChanged) and then it removes the observer. If the LiveData already has a value, it returns it immediately. Additionally, if the value is never set, it will throw an exception after 2 seconds (or whatever you set). This prevents tests that never finish when something goes wrong.

Alternatively, if you need to keep observing a LiveData throughout a test, because it might receive multiple values that you want to check, take a look at observeForTesting in the LiveData sample.

Now the test is much more readable:

Note: While you don’t need to do this with MutableLiveDatas, it’s good practice to getOrAwaitValue when reading any LiveData value in case the implementation detail changes in the future:

You don’t need to observe MutableLiveDatas to get their value, but it’s a good practice.

InstantTaskExecutorRule

It’s important to note that this technique can have threading issues. Most of them can be solved by adding InstantTaskExecutorRule to your unit tests. However, if you call LiveData.postValue() from the main thread, the documented precedence might not be preserved. This is not common but worth mentioning because it can create evasive bugs.

See it in action in this test in the LiveData sample.

Other scenarios

Wrong ViewModel

If you’re using a shared ViewModel between multiple fragments, make sure you’re using the same instance in all screens. This can happen when passing the Fragment instead of the Activity as the LifecycleOwner to the ViewModelProviders or using by ViewModels in a fragment instead of by activityViewModels().

Wrong Room database instance

Why is a query that returns a LiveData not emitting any updates?

If you’re sure that the LiveData is being observed, you might be using different instances of the database. Check your creation patterns or your DI graph. If you know what you’re doing, you can also enable multi-instance invalidation in the database builder, but it has some limitations.

You can see LiveDataTestUtil in action in the LiveDataSample.

What other scenarios have you found? Let us know in the comments!

--

--

Jose Alcérreca
Android Developers

Developer Relations Engineer @ Google, working on Android