You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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:

  1. 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.
  2. 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):

  1. -> at com.impl.SelfServiceCoreClaimImplTest.testGenerateJsonFile_Success(SelfServiceCoreClaimImplTest.java:147)
  2. -> 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)

问题原因与解决方法

第一个错误的核心原因

  1. 直接mock非mock对象的方法:你尝试用when(selfServiceCoreClaimImpl.isSelfServicePortalServerAvailable(...))mock被测类的方法,但selfServiceCoreClaimImpl如果是真实实例而非@Spy或@Mock对象,Mockito无法拦截其方法调用,导致MissingMethodInvocationException。
  2. RestTemplate参数匹配失败:测试中用了具体的HttpEntity实例,但业务方法里会新建一个HttpEntity对象,两个实例不是同一个,导致RestTemplate的mock匹配不上,无法触发预设返回值。

第二个错误的核心原因

  1. 未触发目标代码分支:没有mockisSelfServicePortalServerAvailable返回true,业务方法会走else分支,根本不会调用RestTemplate,导致你预设的RestTemplate stub从未被执行,触发UnnecessaryStubbingException。
  2. 返回值误解:业务方法初始会新建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等对象。
  • 确保触发目标分支:必须mockisSelfServicePortalServerAvailable返回true,才能进入调用RestTemplate的代码分支,避免不必要的stub错误。
  • 避免mock简单DTO:ResponseEntity是简单载体类,直接创建真实实例即可,无需mock。

内容的提问来源于stack exchange,提问作者dhS

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 00:47:03