ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Follow publication

How to easily test a ViewModel with LiveData and Coroutines

Photo by ShareGrid on Unsplash

I know, there are plenty of articles, videos and tutorials explaining how to test ViewModels, LiveData and Coroutines, but after have worked in different projects using them, I’d like to share a way which I consider easy, flexible and fast. I’m not saying that this is the best way, but it has worked for my projects very well.

The ViewModel

For simplicity, let’s start with a viewModel class in which we have a method to query a database (it can be or not Room) and we use LiveData to let the view know that some data is available to be shown (for example querying an object or a list of objects) and we also use coroutines to save the data to the local database. Something like this:

The ViewModel class above then query an Auction object, and set the result to a LiveData<Auction> object that will be observed for changes and react to them. Also the saveAuction method is a coroutine to update the local database, which does all the job out of the main thread, so it won’t be blocked, and then after is completed returns to it.

Testing The ViewModel — LiveData

Firstly, let’s show how to test the queryAuctionById method. To successfully test it, we need to take into consideration that this method updates the value of a LiveData object that is being observed. In order to test a LiveData we have several options such as either using observe forever or using a fake lifecycle, or implementing a test implementation of lifecycle owner, among others. The easiest option for me is building an observer instance that owns its own life-cycle. After handling the onChange event we will mark the observer life-cycle as destroyed and let the framework do the rest. Ale Diaferia explains it very well here.

If we don’t do this, unfortunately, by the time we are asserting on it, the value of the LiveData instance will not be populated and will make our test fail. This is because LiveData uses a lifecycle-oriented asynchronous mechanism to populate the underlying data and expects an observer to be registered in order to inform about data changes.

A custom Observer which only observes once

After this, and taking advantage of Kotlin Extensions, we create a LiveData one to call this observer and use within the context of our test:

Now, with our own observer and an extension to call it when we need it, then our test would look like this:

In line 35, we call the method in ViewModel class that will set the value to the LiveData. Then we observe to it in line 36. There we call the extension function of observeOnce, to finally do the assertion. So, our test of the LiveData object is done. Easy, isn’t it?

Testing The Coroutine

Secondly, we have to test our ViewModel’s save method, the one in which we use a coroutine. When I started using coroutines, I thought that testing them was gonna be a little though, but an easy and functional way to test a coroutine or a suspend function is using the runBlocking function. That is all, this will block the main thread to run all the work there, so the saving process run and after it we query the value and do the assertion:

And that’s all folks. A fast and easy way to test ViewModels which use LiveData and Coroutines. This will save you a lot of time and let you all to implement your tests quicker.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Carlos Daniel

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

Responses (5)

Write a response

As someone else pointed out, this method is not reliable. The assertion is only checked if a value it actually posted. In your case if you remove ` auction.value = auctionQueried`, the test will still pass.

--

Great article but I see one flaw with testing using the extension function and class you wrote. Correct me if I am wrong but by putting the assertion into the handler you’re not truly guaranteeing the assertion passes. You can only guarantee that if…

--