我有Java版本11,JUnit版本4.13.1并尝试将powermock
应用于我的单元测试。我添加了本页提到的必要库:
JUnit 4.4或以上版本:
<properties>
<powermock.version>2.0.2</powermock.version>
</properties>
<dependencies>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
然后更新我的单元测试,如下所示:
DemoUnitTest:
@RunWith(PowerMockRunner.class)
@PrepareForTest(Locale.class)
public class DemoUnitTest {
@InjectMocks
private DemoServiceImpl demoService;
@Test(expected = EntityNotFoundException.class)
public void test_PowerMock() {
String countryCodeUpper = countryCode.toUpperCase(Locale.ENGLISH);
PowerMockito.mockStatic(Locale.class);
BDDMockito.given(Locale.getISOCountries()).willReturn(new String[]{});
demoService.create(countryCode);
}
}
那么,错误在哪里?
下面是堆栈跟踪:
java.lang.Exception: Unexpected exception, expected<com.mycompany.common.domain.exception.EntityNotFoundException> but was<org.mockito.exceptions.misusing.NotAMockException>
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.handleException(PowerMockJUnit44RunnerDelegateImpl.java:380)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.handleException(PowerMockJUnit47RunnerDelegateImpl.java:126)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.handleInvocationTargetException(PowerMockJUnit44RunnerDelegateImpl.java:353)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:331)
at org.junit.internal.runners.MethodRoadie$2.run(MethodRoadie.java:89)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:97)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.executeTest(PowerMockJUnit44RunnerDelegateImpl.java:310)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTestInSuper(PowerMockJUnit47RunnerDelegateImpl.java:131)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.access$100(PowerMockJUnit47RunnerDelegateImpl.java:59)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner$TestExecutorStatement.evaluate(PowerMockJUnit47RunnerDelegateImpl.java:147)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.evaluateStatement(PowerMockJUnit47RunnerDelegateImpl.java:107)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit47RunnerDelegateImpl$PowerMockJUnit47MethodRunner.executeTest(PowerMockJUnit47RunnerDelegateImpl.java:82)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$PowerMockJUnit44MethodRunner.runBeforesThenTestThenAfters(PowerMockJUnit44RunnerDelegateImpl.java:298)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:87)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:50)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.invokeTestMethod(PowerMockJUnit44RunnerDelegateImpl.java:218)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.runMethods(PowerMockJUnit44RunnerDelegateImpl.java:160)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl$1.run(PowerMockJUnit44RunnerDelegateImpl.java:134)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:34)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:44)
at org.powermock.modules.junit4.internal.impl.PowerMockJUnit44RunnerDelegateImpl.run(PowerMockJUnit44RunnerDelegateImpl.java:136)
at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:117)
at org.powermock.modules.junit4.common.internal.impl.AbstractCommonPowerMockRunner.run(AbstractCommonPowerMockRunner.java:57)
at org.powermock.modules.junit4.PowerMockRunner.run(PowerMockRunner.java:59)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: org.mockito.exceptions.misusing.NotAMockException: Argument should be a mock, but is: class java.lang.Class
at com.mycompany.core.service.impl.unit.CountryServiceImplTest.test_PowerMock(CountryServiceImplTest.java:191)
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)
... 5 more
将@PrepareForTest(Locale.class)
更改为@PrepareForTest(DemoServiceImpl.class)
java.util
包的Locale是一个系统类。PowerMockRunner不能拦截它的加载,因为Java已经加载了它。因此,您必须使用特殊规则来模拟您颠倒正常规则的系统类:
通常情况下,您将准备包含您想要模拟的静态方法(我们称之为X)的类,但是由于PowerMock不可能准备用于测试的系统类,因此必须采用另一种方法。因此,不是准备X,而是准备调用X中的静态方法的类!
这使得有一些意义:@PrepareForTest
指示PowerMock应该拦截哪些类并重写字节码。因为您不能拦截和字节码重写Locale,所以下一个最好的方法是拦截和字节码重写您的被测系统,以便它调用PowerMock重写的Locale而不是Java的Locale。
一般来说,模拟测试中的系统被认为是一种不好的做法,但这里有一点例外:只要您实际上没有模拟您自己的测试中的系统,这更像是在您的类中创建一个测试接缝。不存在针对模拟行为进行意外测试而不是测试预期系统的危险。也就是说,您可以考虑重构:
- 您可以将
Locale.getISOCountries()
保存为"可见"以供测试。字段,或者使它成为一个可选的构造函数参数,当不存在时服从Locale.getISOCountries()
。 - 你可以将对
Locale.getISOCountries()
的调用提取到一个lambda(例如Supplier<String[]>
),并同样使其成为一个可见字段或可选的构造函数参数。你可以将调用提取到DemoServiceImpl上的一个可重写的方法中,并在你的测试中重写它,尽管这也冒着实现和测试代码之间界限模糊的风险。