JUnit中RestTemplate Mocking失效问题排查求助
业务方法代码
@Override public ClaimSubmissionResponse generateJsonFileProfessional(String claimType, String correlationId, String jsonContent, String authToken) { ClaimSubmissionResponse claimSubmissionResponse = new ClaimSubmissionResponse(); try { if (isSelfServicePortalServerAvailable(selfServicePortalURL)) { HttpHeaders headers = populateTokenHeaders(authToken); String corelationId = UUID.randomUUID().toString(); InetAddress ipAddress = InetAddress.getLocalHost(); HttpEntity<String> request = new HttpEntity<>(jsonContent, headers); String claimSubmissionAPI = selfServicePortalURL + Endpoints.SUBMIT_CLAIM_PROFESSIONAL; processRequestAudit(jsonContent, corelationId, corelationId, ipAddress.toString(), "Generate Json File", "Claim Submission"); UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(claimSubmissionAPI) .queryParam("claimType", claimType).queryParam("correlationId", correlationId); ResponseEntity<ClaimSubmissionResponse> response = restTemplate.exchange( builder.buildAndExpand().toUri(), HttpMethod.POST, request, ClaimSubmissionResponse.class); claimSubmissionResponse = response.getBody(); processResponseAudit(claimSubmissionResponse, "", corelationId, corelationId); } else { log.info(SELF_SERVICE_PORTAL_UNAVAILABLE_MESSAGE); } } catch (Exception cause) { log.error("The Error While Executing the API Call :: {} ", cause.getMessage()); } return claimSubmissionResponse; }
最初的JUnit测试用例
@Test public void testGenerateJsonFileProfessional_Success() throws Exception { // Mocking external methods and variables String claimType = "type1"; String correlationId = UUID.randomUUID().toString(); String jsonContent = "{}"; String authToken = "token123"; String corelationId = UUID.randomUUID().toString(); InetAddress ipAddress = InetAddress.getLocalHost(); // Mocking isSelfServicePortalServerAvailable when(selfServiceCoreClaimImpl.isSelfServicePortalServerAvailable(selfServicePortalURL)).thenReturn(true); // Mocking populateTokenHeaders when(selfServiceCoreClaimImpl.populateTokenHeaders(authToken)).thenReturn(httpHeaders); // Mocking RestTemplate response ClaimSubmissionResponse expectedResponse = new ClaimSubmissionResponse(); ResponseEntity<ClaimSubmissionResponse> mockResponse = new ResponseEntity<>(expectedResponse, HttpStatus.OK); // UriComponentsBuilder mock setup UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(selfServicePortalURL) .queryParam("claimType", claimType) .queryParam("correlationId", correlationId); when(restTemplateTest.exchange( builder.buildAndExpand().toUri(), HttpMethod.POST, new HttpEntity<>(jsonContent, httpHeaders), ClaimSubmissionResponse.class)) .thenReturn(mockResponse); // Call the method under test ClaimSubmissionResponse actualResponse = selfServiceCoreClaimImpl.generateJsonFileProfessional(claimType, correlationId, jsonContent, authToken); // Assertions assertNotNull(actualResponse); assertEquals(expectedResponse, actualResponse); // Verify RestTemplate was called verify(restTemplateTest).exchange( builder.buildAndExpand().toUri(), HttpMethod.POST, new HttpEntity<>(jsonContent, httpHeaders), ClaimSubmissionResponse.class); }
首次运行错误
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);Also, this error might show up because:
- you stub either of: final/private/equals()/hashCode() methods.
Those methods cannot be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.- inside when() you don't call method on mock but on some other object.
at com.acentra.ssp.impl.SelfServiceCoreClaimImplTest.testGenerateJsonFileProfessional_Success(SelfServiceCoreClaimImplTest.java:170)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
更新后的测试用例
@Test public void testGenerateJsonFile_Success() { ClaimSubmissionResponse mockResponse = new ClaimSubmissionResponse(); ResponseEntity<ClaimSubmissionResponse> responseEntity = mock(ResponseEntity.class); when(responseEntity.getBody()).thenReturn(mockResponse); when(restTemplate.exchange(any(), eq(HttpMethod.POST), any(HttpEntity.class), eq(ClaimSubmissionResponse.class))).thenReturn(responseEntity); ClaimSubmissionResponse response = selfServiceCoreClaimImpl.generateJsonFileProfessional("claimType", "correlationId", "jsonContent", "testdata"); assertNotNull(response); }
更新后运行错误
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
- -> at com.impl.SelfServiceCoreClaimImplTest.testGenerateJsonFile_Success(SelfServiceCoreClaimImplTest.java:147)
- -> at com.impl.SelfServiceCoreClaimImplTest.testGenerateJsonFile_Success(SelfServiceCoreClaimImplTest.java:148)
Please remove unnecessary stubbings or use 'lenient' strictness. More info: javadoc for UnnecessaryStubbingException class.
at org.mockito.junit.jupiter.MockitoExtension.afterEach(MockitoExtension.java:192)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
问题原因与解决方法
第一个错误的核心原因
- 直接mock非mock对象的方法:你尝试用
when(selfServiceCoreClaimImpl.isSelfServicePortalServerAvailable(...))mock被测类的方法,但selfServiceCoreClaimImpl如果是真实实例而非@Spy或@Mock对象,Mockito无法拦截其方法调用,导致MissingMethodInvocationException。 - RestTemplate参数匹配失败:测试中用了具体的
HttpEntity实例,但业务方法里会新建一个HttpEntity对象,两个实例不是同一个,导致RestTemplate的mock匹配不上,无法触发预设返回值。
第二个错误的核心原因
- 未触发目标代码分支:没有mock
isSelfServicePortalServerAvailable返回true,业务方法会走else分支,根本不会调用RestTemplate,导致你预设的RestTemplate stub从未被执行,触发UnnecessaryStubbingException。 - 返回值误解:业务方法初始会新建
ClaimSubmissionResponse对象,除非进入catch分支且被修改,否则不会返回null。实际返回null可能是isSelfServicePortalServerAvailable真实逻辑抛异常,被catch后返回初始对象(你误判为null)。
正确的解决步骤与测试用例
1. 测试类基础配置
确保正确使用Mockito注解:
@ExtendWith(MockitoExtension.class) public class SelfServiceCoreClaimImplTest { // 被测类,自动注入所有@Mock依赖 @InjectMocks private SelfServiceCoreClaimImpl selfServiceCoreClaimImpl; // 依赖的RestTemplate,创建mock对象 @Mock private RestTemplate restTemplate; // 模拟populateTokenHeaders返回的HttpHeaders @Mock private HttpHeaders httpHeaders; // 测试用服务URL,可硬编码或通过@Value注入 private String selfServicePortalURL = "http://test-portal-url"; }
2. 成功场景测试用例
@Test public void testGenerateJsonFileProfessional_Success() throws Exception { // 测试数据 String claimType = "type1"; String correlationId = UUID.randomUUID().toString(); String jsonContent = "{}"; String authToken = "token123"; ClaimSubmissionResponse expectedResponse = new ClaimSubmissionResponse(); // 直接创建真实ResponseEntity,无需mock ResponseEntity<ClaimSubmissionResponse> mockResponseEntity = new ResponseEntity<>(expectedResponse, HttpStatus.OK); // 用doReturn语法mock被测类内部方法,避免触发真实逻辑 doReturn(true).when(selfServiceCoreClaimImpl).isSelfServicePortalServerAvailable(anyString()); doReturn(httpHeaders).when(selfServiceCoreClaimImpl).populateTokenHeaders(eq(authToken)); // 用参数匹配器mock RestTemplate,适配动态创建的参数 when(restTemplate.exchange( any(URI.class), eq(HttpMethod.POST), any(HttpEntity.class), eq(ClaimSubmissionResponse.class) )).thenReturn(mockResponseEntity); // 调用被测方法 ClaimSubmissionResponse actualResponse = selfServiceCoreClaimImpl.generateJsonFileProfessional(claimType, correlationId, jsonContent, authToken); // 断言结果 assertNotNull(actualResponse); assertEquals(expectedResponse, actualResponse); // 验证依赖方法调用次数 verify(restTemplate, times(1)).exchange( any(URI.class), eq(HttpMethod.POST), any(HttpEntity.class), eq(ClaimSubmissionResponse.class) ); verify(selfServiceCoreClaimImpl, times(1)).isSelfServicePortalServerAvailable(eq(selfServicePortalURL)); verify(selfServiceCoreClaimImpl, times(1)).populateTokenHeaders(eq(authToken)); }
关键注意点
- 用
doReturn处理Spy对象:如果被测类是@Spy(部分mock),必须用doReturn(...).when(spy).method(),避免when(spy.method())触发真实方法执行。 - 使用参数匹配器:
any()、eq()等匹配器可避免因对象实例不同导致的匹配失败,适配业务方法中动态创建的HttpEntity、URI等对象。 - 确保触发目标分支:必须mock
isSelfServicePortalServerAvailable返回true,才能进入调用RestTemplate的代码分支,避免不必要的stub错误。 - 避免mock简单DTO:
ResponseEntity是简单载体类,直接创建真实实例即可,无需mock。
内容的提问来源于stack exchange,提问作者dhS

