Unit testing on Android

Deniz Demirci
ProAndroidDev
Published in
6 min readApr 10, 2021

--

Testing your code is crucial because of several reasons and we will see some of those reasons through this article with examples.
This article is going to involve examples of;

  1. Unit test for ViewModels
  2. Unit test for Custom Views
  3. Unit test for Extension Functions

Unit test for ViewModels

We are going to check if our ViewModel does what it is required to do. We are going to call the search() function and we are going to check if the LiveData value is set accordingly.

We are using LiveData here, but everything remains the same with StateFlow

Before we start going into the detail about our view model, we need to remember two very important things.

  1. Unit tests are there to test a specific unit. Since we are testing our view model, our test should not care about what our repository class does, what it returns, or what kind of logic it has inside.
  2. In order to properly test a function, we must eliminate the usage of global parameters as much as we can. It is better if a function has all the parameters that it needs to do its job.

In order to test LiveData, we need to take care of two things.

First, the LiveData should be observed in order to work properly. You can find a utility class at the end of the section to observe your LiveData from test class.

Secondly we should add InstantExecutorRule test rule.
InstantExecutorRule is a JUnit rule and it comes with androidx.arch.core library. JUnit rules are specific definitions that are run before, after or even during each test functions. InstantExecutorRule makes each task executed one after another, prevents asynchronous operations.

Now let’s talk about what MainCoroutineRule is. Just like InstantExecutorRule, MainCoroutineRule is a JUnit rule that swaps the coroutine dispatcher.

Every coroutine needs a coroutine scope to run inside. When the scope is cancelled, the coroutine is cancelled as well. When we are using coroutines inside view models, we are actually using viewModelScope to launch that coroutine.

Then comes the coroutine dispatchers. Coroutine dispatchers are basically the thing that determines which thread the coroutines are going to work on to. The default coroutine dispatcher the viewModelScope has is the Dispatchers.Main and the Dispatchers.Main uses Android’s Main Looper which is not available in the local test environment.

So in order to write unit test for a ViewModel that uses coroutines, we need to swap that dispatcher with a test dispatcher. And the MainCoroutineRule does that. You can find the MainCoroutineRule.kt class at the end of the section and add it to your test source set.

At the @Before block, we instantiate our ViewModel with the mocked repository it needs as a dependency.

Inside the test function, we first create a response that we want to have from repository. Because as mentioned before, we are testing the view model and we don’t care about the repository.

Then we use coEvery function from mockK library. This line of code means that, whenever repository.search() is called with any parameter, no matter what, return me the res value I created above.

co in ‘coEvery’ stands for coroutines, there is also a ‘every’ function which we will see later in the article.

Then we call the viewModel.search() with desired parameters. Eventually, repository.search() is called with provided parameters. Since we used coEvery on that, we get the response we created.

And finally we check if the LiveData value is set accordingly.

Util class provided from Google for testing LiveData
Util class provided from Google for testing coroutines inside ViewModel

Unit test for Custom Views

Creating a custom view can be useful if we are going to use it multiple times in our app. When it comes to testing a custom view, we might have a confusion. They are a part of the UI, they are views. So shouldn’t it be a topic of UI testing (Instrumentation testing) instead of unit testing?

Not entirely. Since unit tests run faster than UI tests, testing as much as we can in a unit test is better. And if we have the correct setup for a custom view test class, then we are good to go.

First let’s take a look at our custom view. They are all instances of our TextField class. Some has white background, some has hints, and the phone field has a flag. And these differences rely on the logic so we can write unit tests for them.

Address Saving Screen that involves the same custom view

Here is what the TextField layouts and the class looks like.

The layout of custom view
The sample usage of the custom field

Note that we provide some custom attributes we predefined in the attrs.xml resource file such as isObligated and textFieldType. Depending on the textFieldType we put the flag if its type is PHONE.

The original custom view class

As you can see in the above code, in the initView() function, we call different functions depending on the type being Default or Phone.

And here comes the test class.

Here in the setup function, we need to do some specifications. The most important thing is to remember this is a unit test. Unit tests run on JVM and is agnostic of Android environment. Therefore we can not directly use Android classes such as Context or ViewTreeObserver or LayoutInflater etc. so we are going to mock them.

You might want to check the documentation to learn about how to use android dependent classes in unit tests using
https://developer.android.com/training/testing/unit-testing/local-unit-tests

Another important note is how we initialize our custom view. We are using spyk which is something between the original object and a mocked object.

And the last thing is to make sure that every time the code tries to reach context of the custom view, we should make it use the mocked context (and the same for viewTreeObserver).

Then we can finally write our test cases and check if the correct method is called with the correct type.

Unit test for Extension Functions

We often create extension functions for common logic we may need on different parts of our app. isCharUnique function checks if a specific char in a given string appears only one time or not.

Since this method involves some logic in it and the logic could be crucial for us, we need to test this method.

With the test function above, we can apply various test scenarios on our extension function isCharUnique. The more scenario we test, the better we can make sure the function works properly in every cases. Use your imagination but still prevent writing unnecessary test cases.

In the above test function, there are some assertions that look identical. But actually they are not.

  1. ‘a’ & ‘d’ cases look similar but the difference is that, one of them is in the beginning of the string, the other one is somewhere in the middle. I thought, since the original method deals with an array and has some logic in it, it would be safer to check both cases.
  2. ‘b’ & ‘c’ look similar as well. The difference here is ‘b’ appears after some letters later and ‘c’ appears one after another and 3 times.

Conclusion

Writing unit tests is important from so many ways.

  1. You will prevent potential bugs, potential crashes, potential user and money loss.
  2. Unit tests can be used as a documentation of your project since it clearly describes the usage of the logic you created.
  3. My favorite thing about writing unit tests is it forces you to write testable code. That way your code is cleaner, more understandable, more maintainable.

To reach out to more unit tests and UI tests, you can visit my Github page and check my repositories.

Thanks for reading and I hope you learned something new, or somethings are settled in your mind better. Always remember to make the below test pass.

verify { you.liveHappily() }

--

--