模拟restTemplate抛出org.mockito.exceptions.misusing.UnfinishedStubbingException:

时间:2019-11-28 10:33:00

标签: java spring-boot unit-testing mockito resttemplate

首先,我尝试编写用于远程服务的单元测试以获取天气并使用restTemplate,然后实现ClientHttpRequestInterceptor以获得用于记录目的的远程请求和远程响应,现在我尝试模拟{ {1}}编写单元测试并得到以下错误

restTemplate
org.mockito.exceptions.misusing.UnfinishedStubbingException: 
Unfinished stubbing detected here:
-> at com.unittest.weather.forcast.service.WeatherRemoteServiceTest.testRetrieveWeather(WeatherRemoteServiceTest.java:55)

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 is completed

    at com.unittest.weather.forcast.service.WeatherRemoteServiceTest.testRetrieveWeather(WeatherRemoteServiceTest.java:55)
    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.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:44)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    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.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:74)
    at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:80)
    at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:39)
    at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
    at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:40)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Iterator.forEachRemaining(Iterator.java:116)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
    at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:71)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:137)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:541)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:763)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:463)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:209)


这是业务逻辑本身的代码

@RunWith(MockitoJUnitRunner.class)
public class WeatherRemoteServiceTest {

    @Mock CustomResttemplate restTemplate;

    @Mock WeatherRemoteUtilies weatherUtilies;

    @InjectMocks WeatherRemoteService weatherRemote;


    @Before
    public void setUp() throws Exception {
    }

    @Test
    public void testRetrieveWeather() {

        MultiValueMap<String, String>multiValue=new LinkedMultiValueMap<>();
        RestTemplate objRestTemplate=new RestTemplate();
        Mockito.when(weatherUtilies.buildRequestParams(Mockito.anyString()))
                        .thenReturn(multiValue);
        doReturn(objRestTemplate).when(restTemplate).buildRestTemplate();
        ResponseEntity<String> responseEntity = new ResponseEntity<String>("sampleBodyString", HttpStatus.OK);
        doReturn(responseEntity).when(restTemplate.buildRestTemplate()).exchange(
                                   Matchers.anyString(), 
                                   Matchers.any(HttpMethod.class),
                                   Matchers.<HttpEntity<?>> any(), 
                                   Matchers.<Class<String>> any()
                                  );
        assertEquals(weatherRemote.retrieveWeather("ciaro").getStatusCode(), HttpStatus.OK);
    }

}

RestTemplateInterceptor

代码

@Service
public class WeatherRemoteService {

    private final Logger logger= LoggerFactory.getLogger(this.getClass());

    @Value("${openweather.url}")
    private String url;

    @Autowired
    private WeatherRemoteUtilies weatherUtilies;

    @Autowired
    private CustomResttemplate customRestTemplate;

    public ResponseEntity<?> retrieveWeather(String city) {

        logger.info(Constants.CLASS_NAME+this.getClass().getName()+Constants.METHOD_NAME+new Object() {}.getClass().getEnclosingMethod().getName());
        logger.debug(Constants.METHOD_ARGUMENTS+city);
        RestTemplate restRequest= customRestTemplate.buildRestTemplate();
        HttpHeaders headers= new HttpHeaders();
        headers.set("Accept",MediaType.APPLICATION_JSON_UTF8_VALUE);
        UriComponentsBuilder uri= UriComponentsBuilder.fromUriString(url).
                path("/data/2.5/weather")
                .queryParams(weatherUtilies.buildRequestParams(city));
        HttpEntity<String>entity= new HttpEntity<>(headers);
        ResponseEntity<String>WeatherResponse=restRequest.exchange(uri.toUriString(), HttpMethod.GET, entity, String.class);
        logger.info(Constants.END_METHOD);
        return WeatherResponse;
    }
}

然后使用以下代码在public class RestTemplateInterceptor implements ClientHttpRequestInterceptor { private final Logger logger =LoggerFactory.getLogger(this.getClass()); @Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { logRequest(request, body); ClientHttpResponse httpResponse= execution.execute(request, body); logResponse(httpResponse); return httpResponse; } private void logRequest(HttpRequest request, byte[] body) throws IOException { logger.info(Constants.START_REMOTE_REQUEST); logger.info("URI :{}", request.getURI().toString()); logger.info("Method :{}", request.getMethod().toString()); logger.info("Headers :{}", request.getHeaders().toString()); logger.info("Request body :{}", new String(body,"UTF-8").toString()); logger.info(Constants.END_REMOTE_REQUEST); } private void logResponse(ClientHttpResponse response) throws IOException { logger.info(Constants.START_REMOTE_RESPONSE); logger.info("Status code :{}", response.getStatusCode().toString()); logger.info("Status text :{}", response.getStatusText().toString()); logger.info("Headers :{}", response.getHeaders().toString()); logger.info("Response body :{}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset())); logger.info(Constants.END_REMOTE_RESPONSE); } } 工厂使用setInterceptor

RestTemplate

1 个答案:

答案 0 :(得分:0)

您拥有的代码无法满足stacktrace中提到的第三个条件:

  
      
  1. 您要在'thenReturn'指令完成之前在内部模拟另一个模拟的行为
  2.   

因为restTemplate.buildRestTemplate()本身是对模拟对象的调用,这使Mockito感到困惑。

即使通过了,您也会在when上使用该objRestTemplate表达式,在您的代码中该表达式不是模拟对象。请注意,行为只能在模拟对象上定义(因此您通常会看到NotAMockException或类似的东西)。


我没有检查其余的代码,但是要解决您提到的异常(至少),需要像下面这样调整测试代码:

@Test
public void testRetrieveWeather() {

    MultiValueMap<String, String>multiValue=new LinkedMultiValueMap<>();

    // use a mock here
    RestTemplate objRestTemplate = mock(RestTemplate.class); 
    Mockito.when(weatherUtilies.buildRequestParams(Mockito.anyString()))
                    .thenReturn(multiValue);
    doReturn(objRestTemplate).when(restTemplate).buildRestTemplate();
    ResponseEntity<String> responseEntity = new ResponseEntity<String>("sampleBodyString", HttpStatus.OK);

    // fix the exception you get by defining behaviour on the mocked object.
    doReturn(responseEntity).when(objRestTemplate).exchange(
                               Matchers.anyString(), 
                               Matchers.any(HttpMethod.class),
                               Matchers.<HttpEntity<?>> any(), 
                               Matchers.<Class<String>> any()
                              );
    assertEquals(weatherRemote.retrieveWeather("ciaro").getStatusCode(), HttpStatus.OK);
}