PowerMockito .when().thenReturn() with randomUUID

2019-05-06 16:18发布

This question already has an answer here:

I'm trying to test a Web Service method which connects to a SQL Server Database which contains JCR nodes, as we're using JackRabbit.

The method looks like:

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;
}

Where jsonResult is an object similar to this one:

{
    "id" : "<A random UUID>"
    "version" : 1
}

Now, when I try to test it following the steps in this answer and the code in this post and I came off with the following code (which as I said is based on the past links):

@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}");
    }
}

However when I test this method it gives me the following results:

Results :

Failed tests: 
    testAddDocumentsByJson(com.optimissa.test.junit.TestJackRabbitService): expected:<{"id":"[1efaf3b8-ca7c-4e6f-878f-102d9a7a92d9]","version":1}> but was:<{"id":"[cfa1a8b0-be6a-46b1-90f5-d2f6d230796a]","version":1}>

As you can see both UUIDs are different, and from what I read on my first link in this question is that it should return the same UUID everytime the static method UUID.randomUUID() is called (the one stored in the uuid variable inside the TestJackRabbitService class...

I also tried with doReturn method as explained in this answer but it produces the following stack trace:

testAddDocumentsByJson(com.optimissa.test.junit.TestJackRabbitService)  Time elapsed: 5.279 sec  <<< ERROR!
org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
-> at com.optimissa.test.junit.TestJackRabbitService.testAddDocumentsByJson(TestJackRabbitService.java:143)

E.g. 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)

From this answer I read (but I don't understand) that maybe I need to create a new object from the class I'm trying to test? I'm injecting the dependency at the very beginning of the test class, I'm really new to JUnit testing and english is not my native language, however I can comprehend most of thing but that answer is giving me a hard time understanding it (due to my lack of knowledge in JUnit testing).

How can I make my JUnit test to retrieve the same ID which is generated inside the method (or intercept the call to UUID.randomUUD() to return the value inside my JUnit test) ?


Edit

After trying @hammerfest's answer, with the following changes:

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);

I still get this result:

testAddDocumentsByJson(com.optimissa.test.junit.TestJackRabbitService): expected:<{"id":"[493410b3-dd0b-4b78-97bf-289f50f6e74f]","version":1}> but was:<{"id":"[00000000-0000-0000-0000-000000000000]","version":1}>

2条回答
小情绪 Triste *
2楼-- · 2019-05-06 17:03

I've reused and modified @hammerfest s example a bit which work on my machine.

The first case simply mocks the static invocation of the UUID class and asserts that the returned UUID of the SUT equals the mocked 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();
    }
  }
}

The second case invokes a method of a Spring managed bean which returns the mocked 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();
    }
  }
}

Both tests work on my machine though I'd suggest to refactor the UUID generation into a utility class which you instantiate and inject via Spring. Then you can simply replace the PowerMock stuff with ordinary Mockito mocking and avoid dealing with such problems:

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());
    }
  }
}

If you don't want to rely on Spring for testing (to speed up things even further) you can inject the dependencies yourself either via constructor injection or via Whitebox.setInternalState(sut, "fieldName", mockObject); or 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();
    }
  }
}

The last test contains both options, constructor or field injection, you can play with.


Due to @hammerfest s comment I'm adding a further example here that showcases what to do if MyClass is externally defined. Note this example was basically taken from Github before I read the answer of @ArthurZagretdinov, who is probably the author of this test in first place (as pointed out by @hammerfest in the comments). First the standalone MyClass implementation:

import java.util.UUID;

public class MyClass {

  public UUID getUUID() {
    return UUID.randomUUID();
  }
}

Next, the test that uses the external MyClass definition:

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())));
  }
}

If you comment out both comment-lines in the above cenario, you will figure out that the test will fail due to unequal UUIDs. This means that the preparation for MyClass does also respect using the declared UUID mock and thus can be used for mocking static classes.

查看更多
一夜七次
3楼-- · 2019-05-06 17:17

Common mistake with mocking system classes it's that they are added to @PrepareForTest, but unfortunately it's impossible to mock final Java System classes directly. But PowerMock provides workaround. PowerMock replaces calls to system classes by call to PowerMock class. A class that use final system class should be added to @PrepareForTest

I've added example how to mock UUID.

public class DocumentService {

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

Test

@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);
  }
}

You may find more in documentation.

查看更多
登录 后发表回答