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
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
- The old
org.mockito.runners.MockitoJUnitRunneris 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
anyInt(), etc. matchers are more strict now, as they do NOT allow
nullvalues 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
- There’s now a distinction between
MockitoHamcrestclasses. If you can’t find your favourite matcher, search for it in the latter.
Case 2 – you use both
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
PowerMockfrom above. Even if you haven’t used PowerMock in any test yet, the dependency alone ensures that PowerMock’s
MockMakeris 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.propertiescontaining the text
mockito.mock-maker-class=mock-maker-inline. Here’s how your test packages should look like now:
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
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.
mozhgan3 years ago
thank you :))))))))))))