PowerMockito .when()。thenReturn(),randomUUID没有返回预期值

我正在尝试测试连接到包含JCR节点的SQL Server数据库的Web服务方法,因为我们正在使用JackRabbit。

该方法如下:

public String addDocumentByJson(String fileName, byte[] fileContent, int status, String userName, String jsonProperties) { UUID id = UUID.randomUUID(); // It does a bunch of operations here return jsonResult; } 

其中jsonResult是一个与此类似的对象:

 { "id" : "" "version" : 1 } 

现在,当我尝试按照本答案中的步骤和本文中的代码测试它时,我发现了以下代码(正如我所说的基于过去的链接):

 @PrepareForTest({ UUID.class }) @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class) @ContextConfiguration("/TestSpringConfig.xml") public class TestJackRabbitService { @Autowired @Qualifier("jackRabbitService") IJackRabbitService jackRabbitService; private byte[] fileContent; private int versionFile; public TestJackRabbitService() { classLoader = getClass().getClassLoader(); } @BeforeClass public static void init() { LOG.trace("Run @BeforeClass"); try { fileContent = IOUtils.toByteArray(new FileInputStream(new File(Thread.currentThread().getContextClassLoader().getResource("fileTest.txt")))); } catch (Exception e) { LOG.error(ExceptionUtils.getStackTrace(e)); } } @Before public void before() { LOG.trace("Run @Before"); try { versionFile = jackRabbitService.getLastVersionOf(nameApp, nameFile); //This method returns an int, } catch (Exception e) { LOG.error(ExceptionUtils.getStackTrace(e)); } } @Test public void testAddDocumentsByJson() { //Some tests which run correctly final UUID uuid = UUID.randomUUID(); mockStatic(UUID.class); LOG.debug(uuid); //doReturn(uuid).when(UUID.randomUUID()); when(UUID.randomUUID()).thenReturn(uuid); idFile = uuid; assertEquals(jackRabbitService.addDocumentByJson(nameFile, bytes, nameApp, 5, jsonproperties), "{\"id\":\"" + uuid + "\",\"version\":1}"); } } 

但是,当我测试这个方法时,它给了我以下结果:

 Results : Failed tests: testAddDocumentsByJson(com.optimissa.test.junit.TestJackRabbitService): expected: but was: 

你可以看到两个UUID是不同的,从我在这个问题的第一个链接上读到的是每次调用静态方法UUID.randomUUID()时应该返回相同的UUID(存储在uuid变量里面的那个) TestJackRabbitService类……

我也尝试使用doReturn方法,如本答案中所述,但它产生以下堆栈跟踪:

 testAddDocumentsByJson(com.optimissa.test.junit.TestJackRabbitService) Time elapsed: 5.279 sec << at com.optimissa.test.junit.TestJackRabbitService.testAddDocumentsByJson(TestJackRabbitService.java:143) Eg thenReturn() may be missing. Examples of correct stubbing: when(mock.isOk()).thenReturn(true); when(mock.isOk()).thenThrow(exception); doThrow(exception).when(mock).someVoidMethod(); Hints: 1. missing thenReturn() 2. you are trying to stub a final method, which is not supported 3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed at org.powermock.core.MockGateway.doMethodCall(MockGateway.java:182) at org.powermock.core.MockGateway.doMethodCall(MockGateway.java:164) at org.powermock.core.MockGateway.methodCall(MockGateway.java:134) at com.optimissa.test.junit.TestJackRabbitService.testAddDocumentsByJson(TestJackRabbitService.java:143) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner$2.call(DelegatingPowerMockRunner.java:149) at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner$2.call(DelegatingPowerMockRunner.java:141) at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner.withContextClassLoader(DelegatingPowerMockRunner.java:132) at org.powermock.modules.junit4.internal.impl.DelegatingPowerMockRunner.run(DelegatingPowerMockRunner.java:141) at org.powermock.modules.junit4.common.internal.impl.JUnit4TestSuiteChunkerImpl.run(JUnit4TestSuiteChunkerImpl.java:121) 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.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:252) at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:141) at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:112) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray(ReflectionUtils.java:189) at org.apache.maven.surefire.booter.ProviderFactory$ProviderProxy.invoke(ProviderFactory.java:165) at org.apache.maven.surefire.booter.ProviderFactory.invokeProvider(ProviderFactory.java:85) at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:115) at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:75) 

从这个答案我读(但我不明白),也许我需要从我试图测试的类创建一个新对象? 我在测试类的最开始注入依赖项,我是JUnit测试的新手,英语不是我的母语,但我能理解大部分内容,但这个答案让我很难理解它(由于我在JUnit测试中缺乏知识)。

如何使我的JUnit测试检索在方法内生成的相同ID(或拦截对UUID.randomUUD()的调用以返回我的JUnit测试中的值)?


编辑

在尝试@ hammerfest的答案后,进行了以下更改:

 UUID uuid = PowerMockito.mock(UUID.class); mockStatic(UUID.class); when(UUID.randomUUID()).thenReturn(uuid); String jsonToCompare = "{\"id\":\"" + uuid + "\",\"version\":1}"; String jsonFromJRS = jackRabbitService.addDocumentByJson(nameFile, bytes, nameApp, 5, jsonproperties); assertEquals(jsonFromJRS, jsonToCompare); 

我仍然得到这个结果:

 testAddDocumentsByJson(com.optimissa.test.junit.TestJackRabbitService): expected: but was: 

模拟系统类的常见错误是它们被添加到@PrepareForTest ,但不幸的是,直接模拟最终的Java System类是不可能的。 但PowerMock提供了解决方法 。 PowerMock通过调用PowerMock类来替换对系统类的调用。 应该使用最终系统类的类添加到@PrepareForTest

我添加了如何模拟UUID的示例 。

 public class DocumentService { public JsonDocument saveDocument(JsonDocument document){ UUID uuid = UUID.randomUUID(); document.setId(uuid.toString()); return document; } } 

测试

 @RunWith(PowerMockRunner.class) @PrepareForTest(DocumentService.class) public class DocumentServiceTest { @Test public void should_set_id() throws Exception { final String id = "493410b3-dd0b-4b78-97bf-289f50f6e74f"; UUID uuid = UUID.fromString(id); mockStatic(UUID.class); when(UUID.randomUUID()).thenReturn(uuid); DocumentService documentService = new DocumentService(); JsonDocument document = new JsonDocument(); documentService.saveDocument(document); assertThat(document.getId()) .as("Id is set") .isEqualTo(id); } } 

您可以在文档中找到更多内容 。

我重新使用并修改了@hammerfest的例子,它可以在我的机器上运行。

第一种情况只是模拟UUID类的静态调用,并声明返回的SUT的UUID等于模拟的UUID:

 import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.UUID; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; @PrepareForTest({ UUID.class }) @RunWith(PowerMockRunner.class) public class StaticMockTest { @Test public void test() { MyClass sut = new MyClass(); UUID uuidLocal = UUID.randomUUID(); mockStatic(UUID.class); when(UUID.randomUUID()).thenReturn(uuidLocal); assertThat(sut.getUUID(), is(equalTo(uuidLocal))); } private class MyClass { public UUID getUUID() { return UUID.randomUUID(); } } } 

第二种情况调用Spring托管bean的方法,该方法返回模拟的UUID:

 import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.modules.junit4.PowerMockRunnerDelegate; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.BootstrapWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import org.springframework.test.context.support.DefaultTestContextBootstrapper; import java.util.UUID; import javax.annotation.Resource; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; @PrepareForTest({ UUID.class }) @RunWith(PowerMockRunner.class) @PowerMockRunnerDelegate(SpringRunner.class) @BootstrapWith(DefaultTestContextBootstrapper.class) @ContextConfiguration(classes = {StaticMockTest2.ContextConfig.class}, loader= AnnotationConfigContextLoader.class) @PowerMockIgnore({"javax.management.*"}) public class StaticMockTest2 { @Resource private MyClass sut; @Test public void test() { UUID uuidLocal = UUID.randomUUID(); mockStatic(UUID.class); when(UUID.randomUUID()).thenReturn(uuidLocal); assertThat(sut.getUUID(), is(equalTo(uuidLocal))); } private static class MyClass { public UUID getUUID() { return UUID.randomUUID(); } } @Configuration public static class ContextConfig { @Bean public MyClass myClass() { return new MyClass(); } } } 

虽然我建议将UUID生成重构为一个实用程序类,然后通过Spring进行实例化和注入,但这两个测试都可以在我的机器上运行。 然后你可以简单地用普通的Mockito模拟替换PowerMock的东西,避免处理这些问题:

 import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; import java.util.UUID; import javax.annotation.Resource; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.powermock.api.mockito.PowerMockito.when; @RunWith(SpringRunner.class) @ContextConfiguration(classes = {MockTest3.ContextConfig.class}, loader= AnnotationConfigContextLoader.class) public class MockTest3 { @Resource private Util mockUtil; @Resource private MyClass sut; @Test public void test() { UUID uuidLocal = UUID.randomUUID(); when(mockUtil.generateUUID()).thenReturn(uuidLocal); assertThat(sut.getUUID(), is(equalTo(uuidLocal))); } private static class MyClass { private Util util; public MyClass(Util util) { this.util = util; } public UUID getUUID() { return util.generateUUID(); } } private static class Util { public UUID generateUUID() { return UUID.randomUUID(); } } @Configuration public static class ContextConfig { @Bean public Util mockUtil() { return mock(Util.class); } @Bean public MyClass myClass() { return new MyClass(mockUtil()); } } } 

如果您不想依赖Spring进行测试(为了进一步加速),您可以通过构造函数注入或通过Whitebox.setInternalState(sut, "fieldName", mockObject);自己注入依赖Whitebox.setInternalState(sut, "fieldName", mockObject); 或Springs ReflectionUtils.setField(sut, "fieldName", mockObject);

 import org.junit.Test; import org.mockito.internal.util.reflection.Whitebox; import java.util.UUID; import javax.annotation.Resource; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import static org.powermock.api.mockito.PowerMockito.when; public class MockTest4 { @Test public void test() { Util mockUtil = mock(Util.class); MyClass sut = new MyClass(mockUtil); // MyClass sut = new MyClass(); // Whitebox.setInternalState(sut, "util", mockUtil); UUID uuidLocal = UUID.randomUUID(); when(mockUtil.generateUUID()).thenReturn(uuidLocal); assertThat(sut.getUUID(), is(equalTo(uuidLocal))); } private class MyClass { @Resource private Util util; public MyClass() {} public MyClass(Util util) { this.util = util; } public UUID getUUID() { return util.generateUUID(); } } private class Util { public UUID generateUUID() { return UUID.randomUUID(); } } } 

最后一个测试包含选项,构造函数或字段注入,您可以使用。


由于@hammerfest的评论,我在这里添加了另一个例子,展示了如果外部定义了MyClass该怎么做。 请注意,在我阅读@ArthurZagretdinov的答案之前,这个例子基本上是从Github中获取的,@ArthurZagretdinov可能是这个测试的作者(正如@hammerfest在评论中指出的那样)。 首先是独立的MyClass实现:

 import java.util.UUID; public class MyClass { public UUID getUUID() { return UUID.randomUUID(); } } 

接下来,使用外部MyClass定义的测试:

 import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import java.util.UUID; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; @PrepareForTest({ MyClass.class }) @RunWith(PowerMockRunner.class) public class StaticMockTest3 { @Test public void test() { MyClass sut = new MyClass(); final String id = "493410b3-dd0b-4b78-97bf-289f50f6e74f"; UUID uuid = UUID.fromString(id); // UUID uuidLocal = UUID.randomUUID(); mockStatic(UUID.class); when(UUID.randomUUID()).thenReturn(uuidLocal); // when(UUID.randomUUID()).thenReturn(uuidLocal); assertThat(sut.getUUID().toString(), is(equalTo(uuid.toString()))); } } 

如果你在上面的cenario中注释掉两个注释行,你会发现测试将因UUID不相等而失败。 这意味着MyClass的准备工作也尊重使用声明的UUID模拟,因此可以用于模拟静态类。