In an ideal world every class in the old codebase would be ready for testing - the dependencies would be provided via the constructor, there would be no Android APIs used inside Presenters…
Sorry to wake you up just now but you’re not living in an ideal world…
The Global pandemic is a thing and people are writing code like there is no tomorrow!

In this article we will talk about what you can do when encountering Android API classes inside objects under test.

Refactor

The best solution and the one that needs to be considered first is trying to refactor the class itself. Having Android API classes in objects that need to be unit tested is usually a code smell - references to Android SDK should, in most circumstances, reside in a given View class (Fragment/Activity).

If you’re familiar with MVP or MVVM architecture you probably know that ViewModels and Presenters should not need to know about details of showing the UI. This allows for a separation of concerns and more testable, easier to understand code.

So in most cases you should try to refactor the code so that it doesn’t contain any Android framework references. It shouldn’t be too hard and you will thank me later on.

Workaround

As I mentioned earlier we’re not living in the ideal world and in some circumstances you won’t be able to refactor the class itself.
I remember that at one point in my career I was tasked with improving the test coverage of our codebase. Despite argumentation management did not allow for refactoring, they just wanted to improve testing coverage fast, probably so it looks good on slides.
In these cases well… you need to suck it up and get your hands dirty!

For the purpose of the article I will use a very simple MainViewModel class along with android.util.Base64 reference.

import android.util.Base64

class MainViewModel : ViewModel() {

    private val mode = Base64.DEFAULT

    fun encodeData(text: String): ByteArray {
        return Base64.encode(text.toByteArray(), mode)
    }
}

The unit test will look like the following

import java.util.Base64

internal class MainViewModelTest {

    private val mainViewModel = MainViewModel()

    @Test
    fun `test encoding`() {
        val text = "text"
        val encodedText = mainViewModel.encodeData(text)
        val decodedText = String(Base64.getDecoder().decode(encodedText))

        assert(text == decodedText)
    }
}

Now if you try to run the code you might see the following errors

java.lang.NullPointerException: Base64.encode(text.toByteArray(), mode) must not be null

or

java.lang.NoClassDefFoundError: android/util/Base64

That happens because tests don’t have access to Android framework classes.
Now lets see whether there is something we can do to fix the error without refactoring MainViewModel.

Create local version of the Android API reference

As mentioned by default the test throws an error when some code under test is not available. However we can workaround this by simply creating our local version of the class that will return what we want.

In our case the missing class is android.util.Base64 and its encode method.

Therefore we can simply create Base64 class inside android.util package which we will place under test directory.

package android.util;

public class Base64 {

    public static byte[] encode(byte[] input, int flags) {
        return java.util.Base64.getEncoder().encode(input);
    }
}

Now the test will pass since the class will be available. That’s it!

In this article we’ve learned what to do when encountering Android API references inside objects under test.