Updating Mockito to mock Kotlin classes

With the exponentially increasing usage of Kotlin these days, many developers face the issue of how to test the newly created Kotlin classes. As we know all classes and methods are final be default in Kotlin, unless specifically open-ed. Unfortunately Mockito, one of the most popular mocking libraries for Java projects, can’t easily mock final classes. Since we don’t want to open up everything just for testing purposes, we need another solution.

Hadi Hariri highlighted in his excellent blog post that Mockito version 2.1.0 and above can perform the magic of mocking final classes. Since mocking is something used only in tests … and usually it just works, we’ve neglected Mockito and were still using a very outdated version (1.10.19) in our project. There were a few pain-points while updating to the latest one, so hopefully this post will save you some time when going through the same process.

Case 1 – Mockito is the only mocking library in your project

That’s the easy case – the update will be smooth, just a few things to note:

  • The name of Mockito’s configuration file you need to add (as described in the linked blog post above) should be exactly test/resources/mockito-extensions/org.mockito.plugins.MockMaker. The text inside should be exactly mock-maker-inline
  • The old org.mockito.runners.MockitoJUnitRunner is deprecated in favour of the new org.mockito.junit.MockitoJUnitRunner (notice the changed package name). Transition is easy – just a mass Find&Replace in Android Studio. The new runner will fail a test class if there’s unneeded mocking in it, so that’s a good opportunity for some little clean-up.
  • All of the any(), anyString(), anyInt(), etc. matchers are more strict now, as they do NOT allow null values anymore. With an old version of Mockito verify(userSettings).setMealPreference(anyString()) is a successful verification if setMealPreference() is called with null, but not anymore. If you still need to allow null values, you can use ArgumentMatchers.nullable() instead.
  • There’s now a distinction between Matchers and MockitoHamcrest classes. If you can’t find your favourite matcher, search for it in the latter.

Case 2 – you use both Mockito and PowerMock

PowerMock alone can mock final classes already! The drawback (in my opinion) is that it requires a bit more ceremony to set up a mock and it’s syntax is a bit more verbose than Mockito’s. Also having the ability to mock static methods might tempt someone to break a few design principles, thus I’d like to remove PowerMock from our testing toolkit completely.

So let’s say you want to continue using Mockito as you were before. Assuming you’ve accounted for all the steps listed in Case 1, these are the gotcha-s here:

  • Don’t forget to import the correct Mockito API for PowerMock (e.g. notice the mockito2 in the dependency) testCompile 'org.powermock:powermock-api-mockito2:1.7.0'. If you just bump the version to 1.7.0 you’ll start getting random exceptions like this:

    java.lang.NoClassDefFoundError: org/mockito/cglib/proxy/Enhancer errors when running a test that uses PowerMock

  • BIG GOTHA -> you’ll notice that mocking final classes with Mockito is still NOT working! That’s because of the dependency to PowerMock from above. Even if you haven’t used PowerMock in any test yet, the dependency alone ensures that PowerMock’s MockMaker is used in your tests (e.g. the configuration done in Case 1 is overwritten). The solution is to instruct PowerMock to use Mockito2’s MockMaker by adding a PowerMock configuration file:
    test/resources/org.powermock.extensions/configuration.properties containing the text mockito.mock-maker-class=mock-maker-inline. Here’s how your test packages should look like now:

Image of the test package structure, showing where to place the configuration files for both Mockito and PowerMock

At this point both PowerMock & mocking final classes should work correctly. Job done, unless your project falls in the third category …

Case 3 – you use Mockito, PowerMock and Robolectric

Assuming you’ve accounted for cases 1 & 2 (as applicable), if you’re using an outdated version of Robolectric, you might start seeing the following errors:

java.lang.IllegalStateException: Could not initialize plugin: interface org.mockito.plugins.MockMaker

	...
Caused by: java.lang.IllegalStateException: Failed to load MockMaker implementation: org.mockito.internal.creation.bytebuddy.InlineByteBuddyMockMaker
	at org.powermock.api.mockito.mockmaker.MockMakerLoader.load(MockMakerLoader.java:39)
	at org.powermock.api.mockito.mockmaker.PowerMockMaker.<init>(PowerMockMaker.java:45)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.lang.Class.newInstance(Class.java:442)
	at org.mockito.internal.configuration.plugins.PluginLoader.loadImpl(PluginLoader.java:96)
	... 36 more
Caused by: org.mockito.exceptions.base.MockitoInitializationException: 
Could not initialize inline Byte Buddy mock maker. (This mock maker is not supported on Android.)
...

Long story short – Mockito 2 “changed the mock maker engine from CGLIB to ByteBuddy” (read more about it HERE). This didn’t work nicely with Robolectric until THIS fix.

Solution – update to the latest version of Robolectric (3.3.2 at the time of writing).

Just to confirm the final combination of library versions that work together nicely:

testCompile 'org.mockito:mockito-core:2.8.9'
testCompile 'org.powermock:powermock-api-mockito2:1.7.0'
testCompile 'org.robolectric:robolectric:3.3.2'

And that’s it – all tests should finally be in the green again. The update of our testing libraries took quite a bit more than my initial estimate of 5 mins, but hopefully this post will save you the extra time.

Happy testing!

Submit a Comment

Your email address will not be published. Required fields are marked *