mockito测试 工具类
tl;dr: If you want to use both Mockito and MockK in the same Android instrumentation test, use this configuration:
tl; dr:如果要在同一Android工具测试中同时使用Mockito和Mock K,请使用以下配置:
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:
我们mySugr使用Mockito已经很多年了。 当编写Kotlin风格的代码时,Mockito感到笨拙,有时需要变通方法和包装器。 由于我们越来越多地使用Kotlin ,尤其是Coroutines ,因此我们需要另一个具有对Coroutines和其他Kotlin功能的一流支持的模拟框架。 我们选择了MockK 。 将所有现有测试迁移到MockK并不是真正的选择。 他们太多了。 这样做并没有真正增加任何价值。 因此,我们决定并行使用MockK和Mockito 。 我们将MockK用于新测试,将Mockito用于现有测试。 这对于单元测试非常有效,但是对于Android上的仪表测试,我们遇到了奇怪的异常,例如:
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.
NoSuchFieldError
表示不应该存在的字段。 这是表明某些依赖关系混乱的有力指标。 当所包含的库对相同库的不同版本具有传递依赖关系时,很容易发生这种情况。 在Java中,每个库(更确切地说是每个完全限定名 )只能存在一次。 这意味着,例如,如果MockK和Mockito都依赖于另一个库的不同版本,则仅加载一个版本。 究竟加载哪个版本由gradle决定。 Gradle默认情况下会自动解决此类版本冲突 。
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.
要查找导致问题的库,我们需要进一步调查。 根据错误消息, No instance field targetApiLevel of type I in class Lcom/android/dx/dex/DexOptions
我们知道该字段在com.android.dx.dex.DexOptions
类中丢失。 因此,此类必须位于加载了错误版本的库中。 Android Studio可以提供帮助,因为它不仅可以在我们自己的项目中找到类,而且可以在依赖项中找到类。 按⌘+ O / Ctrl + N,输入com.android.dx.dex.DexOptions
,并确保在顶部选择了“所有位置” 。

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
:
打开文件后,我们可以在项目视图中看到该类位于依赖项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.
现在我们的假设是在运行时存在com.jakewharton.android.repackaged:dalvik-dx
版本,其中缺少Mockito或MockK所需的必需字段。 在研究传递依赖关系(此处的说明)之后 ,我们发现确实存在版本不匹配。
This is how the transitive path to the dalvik-dx
dependency looks like ("→" means depends on):
这就是到达dalvik-dx
依赖项的传递路径的样子(“→” 取决于 ):
MockK: io.mockk:mockk-android:1.10.0
→ io.mockk:mockk-agent-android:1.10.0
→ com.linkedin.dexmaker:dexmaker:2.21.0
→ com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3
MockK: io.mockk:mockk-android:1.10.0
→ io.mockk:mockk-agent-android:1.10.0
→ com.linkedin.dexmaker:dexmaker:2.21.0
→ com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3
Mockito: org.mockito:mockito-android:3.3.3
→ net.bytebuddy:byte-buddy-android:1.10.5
→ com.jakewharton.android.repackaged:dalvik-dx:1
Mockito: org.mockito:mockito-android:3.3.3
→ net.bytebuddy:byte-buddy-android:1.10.5
→ com.jakewharton.android.repackaged:dalvik-dx:1
We see that MockK depends on dalvik-dx
version 9.0.0_r3
, and Mockito on version 1
.
我们看到MockK依赖于dalvik-dx
版本9.0.0_r3
,以及Mockito依赖版本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:
经过进一步调查,我们发现dalvik-dx:9.0.0_r3
中的targetApiLevel
字段实际上消失了。 因此,我们发现了问题! 但是接下来呢? 最好的主意是检查最新版本的mockito-android
是否( mockk-android
)依赖于与mockk-android
相同的dalvik-dx
版本。 不幸的是,目前还没有适用于mockito-android
较新版本。 但是,有一个新版本的byte-buddy-android
( 1.10.9
)取决于dalvik-dx:9.0.0_r3
。 升级依赖关系的最佳方法是为Mockito开源项目做出贡献,并等待下一个版本的发布。 直到下一个版本与更新的依赖是准备好了,我们可以做一个小黑客:我们告诉gradle这个不包括byte-buddy-android
从的Mockito,而是我们提供了新的版本:
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.
请注意, exclude group
对于包括byte-buddy-android
所有依赖项都是必需的,否则相同的依赖项可能会通过另一个传递性依赖项潜入。
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.
我们了解了如何找到加载了不兼容版本的库以及如何使用gradle替换它。 在这个例子中,我们很幸运。 因为byte-buddy-android
的版本从1.10.5
更改为1.10.9
只是一个补丁,所以没有任何重大更改。 如果不是这种情况,我们可能会收到一个新的错误,其中缺少另一个字段或方法。 版本冲突通常非常棘手,不像在这种情况下那样容易解决。 特别是在生产代码中,我建议您尽可能避免这种“依赖关系替换”,因为它可能导致难以跟踪的细微运行时崩溃。
mockito测试 工具类