Android Testing Tutorial

Writing ViewModel tests in Android -Testing Fundamentals

Don’t stress! Just test!

Saurabh Pant
Dev Genius
Published in
5 min readAug 7, 2022

--

Source: Getty Images/iStockphoto

One of the most common use case in Android is to have a view model in which we’re calling an api and observing its response. Then we process the response and update our UI state. Because this is a very common scenario, it becomes crucial to also test the same. With this scenario, we’ll see how can we write test cases for our view model.

In case you want to read how to set up local tests in Android, please read here.

Set up

We’ll be referencing the DomainLayerDemo project in this article with branch unit-testing. If we’ll look at the weather view model in code, it takes in a use case parameter and the use case processes the api data and emits a flow of type Resource<WeatherData>.

For simplicity, I’ve omitted the hilt annotations, multiple injected repositories but you can refer the project to find the exact code.

Alright! Another important point to note is that view model test cases are preferably written under test folder and not in androidTest folder.

Now that we’re aware of the set up, go ahead and right click on the view model class name and generate the test class.

Now, the very first thing is to know what are we going to test. In our case, we need to test that

  • the data in the UI state shouldn’t be null for success resource
  • the data in the UI state should be null & isLoading should be true for loading resource
  • the data in the UI state should be null & hasError should be true & errorMessage should be Error for error resource

So we’ll name our functions accordingly as follows:

So far so good. Now we see that our use case is an interface with some real data flowing in. But to test our view model, we’re not required to deal with this real data. Instead we need some simulation of the real data. So what should we do?

Fake Use Case

Well! We’ll create a fake implementation of the original use case for the view model which will provide us the control on what to pass into the flow and hence we’ll be able to evaluate our UI state accordingly.

Let’s give this fake implementation an eye.

Line 3: We defined a flow which will return fake flow same as our original use case implementation.

Line 5: emit suspend function will let us define what we want to collect from the fake flow.

Line 7: fetch function simply return our fake flow.

We now go back to our view model test class and here there are two things to be taken care of

  1. launching coroutines in test
  2. evaluating flow in test

For launching coroutines, we need a test coroutine scope which we can provide by adding a dependency

testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2"

and then using the test coroutine builder runTest as follows

The code is very easy to understand. We’re simply passing fake use case to view model and then emitting a success resource. Then we’re checking if we get the data in our UI state or not. This is because in the view model, we’re storing the data into UI state for success resource.

is Resource.Success -> {
_weatherState.value = WeatherScreenState(
data = resource.data
)
}

By this far, if we’ll run this test, it’ll fail with exception

It says that the Main dispatcher is unable to initialize.

This happens because we’re in the test environment and the Main dispatcher that wraps the Android UI thread will be unavailable, as these tests are executed on a local JVM and not an Android device. So we need to set our Dispatcher to Test Dispatcher before launching the coroutine.

Dispatchers.setMain(UnconfinedTestDispatcher())

Ok then, let’s fix the issue and run the test again.

Result of above test case

Whoa! We made it. Let’s write the rest of the test cases and the whole test class looks like as follows

We wrapped common set up to be done for each test under the annotation Before which will prevent the duplication of code. So far so good. We can run our three tests and they all pass.

Bonus: Rule

We know that the main dispatcher has to be switched to test dispatcher to run our tests. For the same we can also create a rule under which we can state that the dispatcher is to be changed to test one and should be reset when finished.

A simple Rule class to be defined. Now this rule can be added in our view model test class as follows

@get: Rule
val dispatcherRule = TestDispatcherRule()

And our Before set up will now be as

@Before
fun setUp() {
fakeWeatherUseCase = FakeWeatherUseCase()
viewModel = WeatherViewModel(fakeWeatherUseCase)
}

Rest remain same and running all the tests will result as follows

Bamn! We just tested our view model for observing a flow and updating UI state. It feels great. You can try playing around with more scenarios and test them to be more confident about the code you write.

Checkout the complete code at:

Bonus Read

That is all for now! Stay tuned!

Connect with me (if the content is helpful to you ) on

Subscribe to email to be in sync for further interesting topics on Android/IOS/Backend/Web.

Until next time…

Cheers!

--

--

App Developer (Native & Flutter) | Mentor | Writer | Youtuber @_zaqua | Traveller | Content Author & Course Instructor @Droidcon | Frontend Lead @MahilaMoney