How to use Mockito together with MockK in Android instrumentation tests

Daniel Gehrer
Monster Culture
4 min readApr 24, 2020

--

tl;dr: If you want to use both Mockito and MockK in the same Android instrumentation test, use this configuration:

androidTestImplementation "io.mockk:mockk-android:1.10.0"
androidTestImplementation("org.mockito:mockito-android:3.3.3") {
exclude group: "net.bytebuddy", module: "byte-buddy-android"
}
androidTestImplementation "net.bytebuddy:byte-buddy-android:1.10.9"

Background

We at mySugr use Mockito already for years. Mockito feels a bit clunky when writing Kotlin-style code and sometimes workarounds and wrappers are needed. Since we’re using Kotlin and especially Coroutines more and more, we need another mocking framework that has first-class support for Coroutines and other Kotlin features. We chose MockK. Migrating all existing tests to MockK is not really an option. There are just too many of them. And there is not really a value added in doing so. So we decided to use MockK and Mockito in parallel. We use MockK for new tests and keep Mockito for existing tests. This works well for unit tests, but for instrumentation tests on Android, we got strange exceptions like:

Example Stacktrace when using Mockito and MockK together.

NoSuchFieldError means that a field that should exist isn't. This is a strong indicator that some dependencies got messed up. That can easily happen when the included libraries have transitive dependencies to different versions of the same library. In Java, each library (more precisely each fully qualified name) can only exist once. That means if e.g. MockK and Mockito both depend on different versions of another library, only one version is loaded. Which version exactly is loaded is decided by gradle. Gradle resolves such version conflicts automatically under the hood by default.

In our case, the version conflict resolution led to a runtime crash. Because the version that was included had breaking changes to the other version. Their result was that an expected field couldn’t be found.

Investigation

To find the library that causes problems, we need to investigate further. From the error, No instance field targetApiLevel of type I in class Lcom/android/dx/dex/DexOptions we know that the field is missing in the class com.android.dx.dex.DexOptions. So this class must be in the library where the wrong version is loaded. Android Studio helps because it can not only find classes in our own projects, but also in dependencies. Press ⌘+O/Ctrl+N, type com.android.dx.dex.DexOptions , and make sure "All places" is selected at the top.

When the file is opened, we can see in the Project View that the class is located in the dependency com.jakewharton.android.repackaged:dalvik-dx:1:

Our assumption is now that a version of com.jakewharton.android.repackaged:dalvik-dx is present at runtime, where a needed field is missing that is needed by either Mockito or MockK. After investigating transitive dependencies (instructions here), we see that there is really a version mismatch.

This is how the transitive path to the dalvik-dx dependency looks like ("→" means depends on):

MockK: io.mockk:mockk-android:1.10.0io.mockk:mockk-agent-android:1.10.0com.linkedin.dexmaker:dexmaker:2.21.0com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3

Mockito: org.mockito:mockito-android:3.3.3net.bytebuddy:byte-buddy-android:1.10.5com.jakewharton.android.repackaged:dalvik-dx:1

We see that MockK depends on dalvik-dx version 9.0.0_r3 , and Mockito on version 1 .

After investigating further, we see that targetApiLevel field is actually gone in dalvik-dx:9.0.0_r3. So we found the issue! But what's next? The best idea would be to check if the newest version of mockito-android depends (transitively) on the same dalvik-dx version as mockk-android. Unfortunately, at the moment there is no newer version for mockito-android. However, there is a newer version of byte-buddy-android ( 1.10.9) that depends on dalvik-dx:9.0.0_r3. The best way to upgrade that dependency would be to contribute to the Mockito open source project and wait for the next release. Until the next release with the updated dependency is ready, we can do a little hack: We tell gradle to not include byte-buddy-android from Mockito, but instead, we provide the newer version:

androidTestImplementation("org.mockito:mockito-android:3.3.3") {
exclude group: "net.bytebuddy", module: "byte-buddy-android"
}
androidTestImplementation "net.bytebuddy:byte-buddy-android:1.10.9"

Note that the exclude group is necessary for all dependencies that include byte-buddy-android, otherwise the same dependency may sneak in via another transitive dependency.

After running the tests again, all tests succeed without the error. We fixed the problem!

Conclusion

We learned how we find the library where an incompatible version is loaded and how to replace it using gradle. In this example, we were very lucky. Because the change in the version of byte-buddy-android from 1.10.5 to 1.10.9 is only a patch, meaning there aren't any breaking changes. If that isn't the case, we might get a new error, where another field or method is missing. Version conflicts are usually very nasty and not as easily resolved as in this case. Especially in production code, I advise you to avoid such "dependency replacements" as much as possible since it can lead to subtle runtime crashes that are hard to track down.

--

--