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

mock restTemplate throws org.mockito.exceptions.misusing.UnfinishedStubbingException:

首先,我尝试为远程服务编写单元测试以获取天气并使用 restTemplate 然后实现 ClientHttpRequestInterceptor 以获取远程请求和远程响应以进行日志记录,现在我尝试mock 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.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.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.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access[=11=]0(ParentRunner.java:58)
    at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.mockito.internal.runners.DefaultInternalRunner.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.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(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);
    }

}

这里是业务逻辑本身的代码

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

RestTemplateInterceptor

的代码
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);
    }
}

然后在 RestTemplate 工厂上使用以下代码设置拦截器

public class CustomResttemplate {

    public RestTemplate buildRestTemplate() {
        SimpleClientHttpRequestFactory simpleFactory= new SimpleClientHttpRequestFactory();
        simpleFactory.setOutputStreaming(false);
        ClientHttpRequestFactory factory = new BufferingClientHttpRequestFactory(simpleFactory);

        RestTemplate restTemplate= new RestTemplate(factory);
        restTemplate.setInterceptors(Collections.singletonList(new RestTemplateInterceptor()));

        return restTemplate;
    }
}

您的代码不满足堆栈跟踪中提到的第三个条件:

  1. you are stubbing the behaviour of another mock inside before 'thenReturn' instruction is completed

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

即使它通过了,您也会在 objRestTemplate 上使用 when 表达式,这在您的代码中不是模拟对象。请注意,行为只能在模拟对象上定义(因此您通常会看到 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);
}