Tricky Android


Android tips, tricks and everything I found interesting


Using Mockito with Kotlin

Warning

After I published this article (thanks to all people who provided their feedback), I realized that approach I suggested is less than ideal and doesn't really solve anything - see updates at the bottom of the article. I will still keep it published though, so other people can learn from my mistakes

As Kotlin gets more and more popular and more people start paying a bit more attention to it, I decided to address one of the long-running pain points for those who tried to test Kotlin code using Mockito.

All Kotlin classes are final by default

Yes. They are really final by default. And even though it is a nice language feature, it puts some constrains on us when it comes to using some well-known libraries and frameworks on Android.

One of such frameworks is Mockito.

If you used Mockito before, you might already know that in order to mock specific class, it cannot be final.

I.e. following code wouldn't just work:

app/src/main/kotlin/Utils.kt:

class Utils {  
    fun getHelloWorld() = "Hello World!"
}

app/src/test/kotlin/UtilsTest.kt:

class UtilsTest {  
    @Test
    @Throws(Exception::class)
    fun testMockedHelloWorld() {
        val mockedUtils = mock(Utils::class.java)
        `when`(mockedUtils.getHelloWorld()).thenReturn("Hello mocked world!")
        val helloWorld = mockedUtils.getHelloWorld()
        assertEquals("Hello mocked world!", helloWorld)
    }
}

Code above would just throw something like this:

org.mockito.exceptions.base.MockitoException:  
Cannot mock/spy class Utils  
Mockito cannot mock/spy following:  
  - final classes
  - anonymous classes
  - primitive types

So here we go. Since Kotlin classes by default are final - Mockito simply cannot mock them.

To "fix" this we could just "open" our class, couldn't we?

app/src/main/kotlin/Utils.kt:

open class Utils {  
    open fun getHelloWorld() = "Hello World!"
}

Life is great again! Tests are running, Mockito is mocking. Job is done! Time to get back to funny cat videos!

Well, not quite!
Needless to say that "opening" your production class just so you can mock it during tests is.... reckless?

Meet all-open plugin

Fear no more! As of Kotlin 1.0.6, you can use all-open compiler plugin which makes classes annotated with a specific annotation and their members open without the explicit open keyword.

Let's look a bit closer at how to use this plugin.

First, let's declare our custom annotation:

app/src/main/kotlin/com/trickyandroid/myapp/TestOpen.kt

package com.trickyandroid.myapp

@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
annotation class TestOpen  

Now, let's apply and configure all-open plugin:

build.gradle:

buildscript {  
    ext.kotlin_version = '1.0.6'
    repositories {
        jcenter()
    }
    dependencies {
        ...
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
    }
}

app/build.gradle:

apply plugin: 'kotlin-allopen'

allOpen {  
    annotation("com.trickyandroid.myapp.TestOpen")
}

Now, let's annotate util class with our annotation:

app/src/main/kotlin/Utils.kt:

import com.trickyandroid.myapp.TestOpen

@TestOpen
class Utils {  
    fun getHelloWorld() = "Hello World!"
}

And voilĂ ! Mockito is happy again and we kept our utility class implementation-private for outer world.

P.S. just don't forget to clean your project after applying all-open plugin.

Update

As pointed out by early readers, there are some alternatives available to this approach.

Mockito opt-in final class mocking

Thanks to @nhaarman for pointing this out!
Apparently, as of Mockito 2.1.0, Mockito supports an experimental feature which allows you to mock final classes.

The only thing you need to do is to add following file into your project:

src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

mock-maker-inline  

This approach seems less invasive since you no longer need to modify your production code to meet test requirements.

However, as mentioned in Mockito documentation, this feature is incubating and has different limitations. I didn't have a chance to thoroughly test this approach, but since it uses Java agent approach (similar approach as implemented in Jacoco coverage reports), I would assume you might encounter some problems with bytecode manipulation plugins (like Jacoco for instance).

Please let me know in comments if you had any problems with this feature.

Use interfaces

Another tip from @artem_zin is to use interfaces, so your implementation implements such interface and during test you need to mock interface instead of concrete final class.

Even though I personally prefer and use this pattern in my code, unfortunately it only applies to the code you own. You still need to solve final class problem when you try to mock third party library class.

But as I think about this more - when it comes to third party classes - you cannot annotate those anyway with your custom annotation.

Classes become open not only during tests

As pointed out by various people, all-open plugin actually opens up classes not only for test configuration. It opens them up everywhere, so please beware of that!

comments powered by Disqus