JUnit中doReturn模拟RestTemplate.exchange返回类型错误问题求助
解决Mockito模拟RestTemplate.exchange时不同泛型返回值冲突的问题
我之前也碰到过一模一样的问题!核心原因是Java泛型的类型擦除在搞鬼,再加上你用了太宽泛的参数匹配,导致Mockito分不清两个exchange调用,最终第二个模拟规则根本没生效。
让我给你拆解下问题,再一步步解决:
问题出在哪?
- 泛型擦除的坑:RestTemplate的
exchange方法里的Class<T>参数,在运行时会被擦除成Class类型——也就是说,不管你传的是ResponseType1.class还是ResponseType2.class,JVM看来这两个方法调用的签名是完全一样的。Mockito没办法通过泛型类型来区分这两个调用。 - 匹配参数太模糊:你用了
anyString()、any(HttpMethod.class)这种模糊匹配,相当于告诉Mockito"所有exchange调用都用这个返回值"。当你配置了两个这样的规则,Mockito只会优先匹配先配置的(或者覆盖为最后一个,取决于版本),自然第二个调用会拿到第一个的返回值。 - 额外小问题:你的模拟返回值类型错了!
exchange方法返回的是ResponseEntity<T>,但你直接返回了ResponseType1实例,这运行起来肯定会抛类型转换异常的,只是现在被泛型冲突的问题掩盖了。
怎么解决?
方案1:用精确参数匹配(最推荐)
既然两个调用的URL、HttpMethod都不一样,我们就用这些明确的特征来区分:
@Test public void test() throws Exception { // 先准备好正确的ResponseEntity返回值 ResponseEntity<ResponseType1> mockResp1 = new ResponseEntity<>(new ResponseType1(), HttpStatus.OK); ResponseEntity<ResponseType2> mockResp2 = new ResponseEntity<>(new ResponseType2(), HttpStatus.OK); // 精准匹配method1里的GET请求:指定url、请求方法、响应类型 doReturn(mockResp1) .when(restTemplate) .exchange( ArgumentMatchers.eq("真实的url1地址"), // 替换成你代码里的url1 ArgumentMatchers.eq(HttpMethod.GET), ArgumentMatchers.any(HttpEntity.class), ArgumentMatchers.eq(ResponseType1.class) ); // 精准匹配method2里的POST请求:指定url、请求方法、响应类型 doReturn(mockResp2) .when(restTemplate) .exchange( ArgumentMatchers.eq("真实的url2地址"), // 替换成你代码里的url2 ArgumentMatchers.eq(HttpMethod.POST), ArgumentMatchers.any(HttpEntity.class), ArgumentMatchers.eq(ResponseType2.class) ); // 执行你的测试逻辑 mainResponse result = yourServiceInstance.add(request, string1, headers, list); // 这里可以加断言验证结果 }
这样Mockito就能精准识别每个调用对应的模拟规则,不会再搞混。
方案2:用thenAnswer动态判断(如果URL是动态的)
如果你的url1/url2是动态生成的,没法用eq()固定匹配,可以用thenAnswer根据传入的响应类型Class来返回对应的值:
@Test public void test() throws Exception { doAnswer(invocation -> { // 获取第四个参数:就是你传的ResponseType1.class/ResponseType2.class Class<?> respType = invocation.getArgument(3); if (respType == ResponseType1.class) { return new ResponseEntity<>(new ResponseType1(), HttpStatus.OK); } else if (respType == ResponseType2.class) { return new ResponseEntity<>(new ResponseType2(), HttpStatus.OK); } // 意外情况抛出异常,方便排查 throw new IllegalArgumentException("没想到的响应类型:" + respType.getName()); }).when(restTemplate) .exchange( ArgumentMatchers.anyString(), ArgumentMatchers.any(HttpMethod.class), ArgumentMatchers.any(), ArgumentMatchers.any(Class.class) ); // 执行测试逻辑 mainResponse result = yourServiceInstance.add(request, string1, headers, list); }
方案3:用MockRestServiceServer(Spring Boot专属,更优雅)
如果你用的是Spring Boot,强烈建议用MockRestServiceServer来模拟RestTemplate的调用——它是Spring官方提供的,专门针对REST调用的模拟工具,完全避开Mockito的泛型匹配问题:
@SpringBootTest class YourServiceTest { @Autowired private YourService yourService; @Autowired private RestTemplate restTemplate; private MockRestServiceServer mockServer; @BeforeEach void setUp() { // 创建模拟服务器绑定到RestTemplate mockServer = MockRestServiceServer.createServer(restTemplate); } @Test void testAdd() throws Exception { // 模拟method1的GET请求 mockServer.expect(MockRestRequestMatchers.requestTo("真实的url1地址")) .andExpect(MockRestRequestMatchers.method(HttpMethod.GET)) .andRespond(MockRestResponseCreators.withSuccess(new ResponseType1(), MediaType.APPLICATION_JSON)); // 模拟method2的POST请求 mockServer.expect(MockRestRequestMatchers.requestTo("真实的url2地址")) .andExpect(MockRestRequestMatchers.method(HttpMethod.POST)) .andRespond(MockRestResponseCreators.withSuccess(new ResponseType2(), MediaType.APPLICATION_JSON)); // 执行测试 mainResponse result = yourService.add(request, string1, headers, list); // 验证所有预期的请求都被调用过 mockServer.verify(); } }
最后提醒
一定要确保模拟返回的是ResponseEntity<T>类型,而不是直接返回ResponseType1/ResponseType2,否则运行时会抛出ClassCastException,这个坑很容易踩!
内容的提问来源于stack exchange,提问作者Swathi
相关产品推荐
相关产品推荐

