Mockito与PowerMock使用问题:Argument should be a mock, but is: class java.lang.Class
问题描述
对公司项目的底层依赖包进行升级,在运行成功后打包时出现了问题,测试无法通过,抛出异常:
org.mockito.exceptions.misusing.NotAMockException: Argument should be a mock, but is: class java.lang.Class
at com.eastmoney.emis.service.IPOServiceTest.getEmptyContentTest(IPOServiceTest.java:132)
at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
问题分析
这次底层包升级虽然动了部分代码和工具,但并没有动测试部分代码,初步锁定在依赖包的问题上。
搜索了一下网上类似的错误,基本上都是说Powermock
本身就存在bug
:
查看作者对于这个bug
的一段描述:
When using Powermocks mockStatic and then calling the when(…).thenReturn(…) method combination of Powermock, the thenReturn method will fail due to a change in Mockito 2.26.1.
See mockito/[email protected]#diff-e4b9e3c44b9c51ed8b23dd318259b1aaR51
As you can see the isVoid check has changed to gather some information from the mock contained in the invocation object.
Unfortunately this mock is not the mocked object but instead the original class which was used to call the static method.
Due to that Mockito complains about this viaorg.mockito.exceptions.misusing.NotAMockException: Argument should be a mock, but is: class java.lang.Class
也就是说2.26.1
版本后,isVoid
已经更改为从调用对象包含的mock
中获取信息。但是,这个mock
不是模拟的对象,而是用来调用静态方法的原始类。所以,这个使用模拟对象传入会抛出上述错误。
大致解决方案是将Mockito
和Powermock
版本对应。
项目中依赖包如下:
<!-- PowerMock JUnit 4.4+ Module -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
</dependency>
明显Mockito
版本过高,但是项目原先使用并无问题,所以问题一定是出现在版本升级的依赖包里。
升级Springboot
的主包:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.7.RELEASE</version>
</parent>
将2.1.7.RELEASE
升为2.3.3.RELEASE
。
升级公司的基础包:
<dependency>
<groupId>com.eastmoney.emis</groupId>
<artifactId>emis-spring-boot-starter</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
将2.1.6.RELEASE
升为2.2.1.RELEASE
。
由上面的描述可以知道出现问题的是org.mockito.internal.stubbing.answers.InvocationInfo.isVoid
,在mockito-core
包下。而包含这个子包的有spring-boot-starter-parent
、mockito-inline
、powermock-api-mockito2
。
根据Maven
的加载顺序可知,此时我们项目加载的是Springboot
中自带的包,可以直接从项目的Maven
库中找到,在升级之前,引用的为mockito-core:2.23.4
。在升级后,引用的为mockito.core:3.3.3
,版本过高,与PowerMock
版本不匹配。
问题解决
解决办法很简单,直接声明mockito-core
的版本就行:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.23.4</version>
</dependency>
问题补充
为简化依赖包,我们可以直接将mockito-inline
用mockito-core
替代即可,但是又会出现问题:
java.lang.RuntimeException: Invoking the beforeTestMethod method on PowerMock test listener [email protected] failed.
...
Caused by: org.mockito.exceptions.base.MockitoException:
Cannot mock/spy class sun.security.rsa.RSAPublicKeyImpl
Mockito cannot mock/spy because :
- final class
...
此时又会出现因为mockito
版本太低不支持final
类的模拟,需要进行版本升级。
解决方案有几种:
- 不更换依赖包,对代码进行一定的调整(不推荐)
- 调整依赖包版本,在保证版本足够高时,同时也与
powermock
版本相匹配,并调整测试代码(推荐:虽然比较复杂耗时,但一劳永逸)