Unit-testing LiveData and other common observability problems
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.
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:
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
observeForTestingin 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:
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.
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
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.
What other scenarios have you found? Let us know in the comments!