Mocking Kotlin classes with Mockito — the fast way

Brais Gabín Moreira
21 Buttons Engineering
3 min readMar 6, 2018

Mockito can’t mock final classes and, by default, all classes in Kotlin are final. Let’s see how to workaround this issue without any speed decrease.

Ok, we are going to start with an example: you are moving from Java to Kotlin and you write your first two classes in Kotlin class:

class Foo {
fun calculateTheFoo(): Int {
sleep(1_000) // Difficult things here
return 1
}
}

class Bar(private val foo: Foo) {
fun calculateDoubleFoo() = foo.calculateTheFoo() * 2
}

And now you want to test Bar:

@Test
fun calculateDoubleFooTest() {
val foo = mock(Foo::class.java)
`when`(foo.calculateTheFoo()).thenReturn(2)
val bar = Bar(foo)
assertThat(bar.calculateDoubleFoo(), `is`(4))
}

You run your tests and get this error:

org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class com.example.Foo
Mockito cannot mock/spy because :
— final class

This is normal; Mockito by default can’t mock a final class. But you have a few options to solve this problem:

The naïve solution

Make Foo and Foo.calculateTheFoo() open (yes, you must write open twice). This solution works perfectly but you are going against the language. Kotlin wants close by default, and you are writing open everywhere. So, for us, this is not a viable option.

Mockito's mock-maker-inline plugin

Use the mock-maker-inline from Mockito as explained by Hadi Hariri in this post. It works perfectly and you don’t need to change anything in your production code.

The problem is the performance. This new mock process is really slow, between 1.5 and 3 times slower. Therefore, I opened a issue in the Mockito Repo to report this performance problem and the answer was:

Inline mocks have to redefine classes what is expensive. The inline mock maker will therefore always be slower, unfortunately.

Kotlin’s all-open plugin

At this point I thought that maybe kotlinc had a flag to change the “all final by default” to “all open by default”. I “researched” a bit and I found that there wasn’t that flag, but there was a plugin to do more less the same: allopen.

The all-open compiler plugin adapts Kotlin […] and makes classes annotated with a specific annotation and their members open without the explicit open keyword.

How does it work? First of all, you must create an annotation, that’s really easy with Kotlin:

annotation class Mockable

Now you need to configure allopen in our build.gradle file:

dependencies {
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
}

apply plugin: 'kotlin-allopen'

allOpen {
annotation('com.example.Mockable')
}

And the last step is to annotate the class that you want to mock:

@Mockable
class Foo {
fun calculateTheFoo(): Int {
sleep(1_000) // Difficult things here
return 1
}
}

That’s it! It works and there isn’t any performance decrease.

The downside of this option is that you must add a small change in your production code to mock it. That’s not ideal, but this is cleaner than the first option (writing open). If you remove the annotation, the compiler will point out all the annotation usages, so you can clean your production code easily.

Comparison between mock-maker-inline and all-open

Measuring the speed improvements in our main project with 2,668 unit tests, the option with mock-maker-inline takes 20.0 seconds and with the all-open solution 7.3 seconds. So our tests run 2.7 times faster now. On the other hand, we saw a little increase in compilation time, 0.5 seconds more. Anyway, the all-open option is much faster as you can see in the graphic.

If you are using Mockito and Kotlin, I recommend you use the all-open plugin to speed up all your tests and improve your productivity. Happy coding!

Update: if you want to disable the all-open plugin in release builds check this StackOverflow answer.

--

--