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

JUnit5+Mockito模拟外部API调用时RestTemplate.exchange打桩返回空问题

问题根源与解决方案

你的问题出在两个核心点上:

  1. 方法内部直接new RestTemplate(),Mockito无法模拟这种局部创建的对象——它只能拦截被注入或传入的实例。
  2. ParameterizedTypeReference是匿名内部类,测试中手动创建的同类型匿名实例,Mockito默认匹配逻辑会判定为不同对象,导致打桩不生效。

第一步:重构原方法,让RestTemplate可被模拟

方案1:将RestTemplate作为方法参数传入

修改方法签名,把RestTemplate从内部创建改为外部传入,测试时即可传入Mock对象:

public static List<TapDetailPojo> getTapPostList(HttpServletRequest request, 
                                                 String apiUrl, 
                                                 String xApiKey, 
                                                 HttpServletResponse response,
                                                 RestTemplate restTemplate) { // 新增参数
    // 移除原代码中的 RestTemplate restTemplate = new RestTemplate();
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.set("x-api-key",xApiKey);
    httpHeaders.setContentType(MediaType.APPLICATION_JSON);
    List<TapDetailPojo> list = null;
    HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders);
    try {
        ResponseEntity<List<TapDetailPojo>> entityResult = restTemplate.exchange(apiUrl, HttpMethod.GET, httpEntity,
                new ParameterizedTypeReference<List<TapDetailPojo>>() {});
        list = entityResult.getBody();
    }catch (HttpClientErrorException exe){
        printInputPojo(httpEntity, "Error", apiUrl);
    }catch(Exception e){
        printInputPojo(httpEntity, "Error", apiUrl);
        e.printStackTrace();
    }
    return list;
}

方案2:类成员变量构造注入(更推荐)

如果该方法属于某个业务类,将RestTemplate作为类成员变量通过构造注入,符合依赖注入原则:

public class TapService {
    private final RestTemplate restTemplate;

    // 构造注入RestTemplate
    public TapService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public List<TapDetailPojo> getTapPostList(HttpServletRequest request, 
                                             String apiUrl, 
                                             String xApiKey, 
                                             HttpServletResponse response) {
        // 直接使用成员变量restTemplate,无需内部new
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.set("x-api-key",xApiKey);
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        List<TapDetailPojo> list = null;
        HttpEntity<String> httpEntity = new HttpEntity<>(httpHeaders);
        try {
            ResponseEntity<List<TapDetailPojo>> entityResult = restTemplate.exchange(apiUrl, HttpMethod.GET, httpEntity,
                    new ParameterizedTypeReference<List<TapDetailPojo>>() {});
            list = entityResult.getBody();
        }catch (HttpClientErrorException exe){
            printInputPojo(httpEntity, "Error", apiUrl);
        }catch(Exception e){
            printInputPojo(httpEntity, "Error", apiUrl);
            e.printStackTrace();
        }
        return list;
    }
}

第二步:编写正确的Mockito测试用例

使用JUnit5的MockitoExtension模拟依赖,针对ParameterizedTypeReference的匹配问题,用any()或自定义匹配器处理:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.*;

@ExtendWith(MockitoExtension.class)
public class TapServiceTest {

    @Mock
    private RestTemplate restTemplate;

    @Mock
    private HttpServletRequest request;

    @Mock
    private HttpServletResponse response;

    @Test
    void getTapPostList_ShouldReturnTapDetails() {
        // 准备测试数据
        List<TapDetailPojo> mockTapList = List.of(new TapDetailPojo(), new TapDetailPojo());
        ResponseEntity<List<TapDetailPojo>> mockResponse = ResponseEntity.ok(mockTapList);
        String testApiUrl = "http://test.api.com/taps";
        String testXApiKey = "test-key";

        // 打桩exchange方法:用any()匹配ParameterizedTypeReference
        when(restTemplate.exchange(
                eq(testApiUrl),
                eq(HttpMethod.GET),
                any(HttpEntity.class),
                any(ParameterizedTypeReference.class)
        )).thenReturn(mockResponse);

        // 静态方法调用方式
        // List<TapDetailPojo> result = YourStaticClass.getTapPostList(request, testApiUrl, testXApiKey, response, restTemplate);

        // 成员方法调用方式(推荐)
        TapService service = new TapService(restTemplate);
        List<TapDetailPojo> result = service.getTapPostList(request, testApiUrl, testXApiKey, response);

        // 断言结果
        assertNotNull(result);
        assertEquals(2, result.size());
    }
}

可选:精确匹配ParameterizedTypeReference

如果需要确保匹配的是List<TapDetailPojo>类型,可自定义匹配器:

when(restTemplate.exchange(
        eq(testApiUrl),
        eq(HttpMethod.GET),
        any(HttpEntity.class),
        argThat(typeRef -> 
            typeRef.getType().getTypeName().equals(
                List.class.getName() + "<" + TapDetailPojo.class.getName() + ">"
            )
        )
)).thenReturn(mockResponse);

额外提示

  • 不推荐用PowerMock模拟局部new的对象,会增加测试复杂度且破坏代码设计原则,重构依赖注入是更优雅的解决方案。
  • 确保测试中所有依赖对象(如HttpServletRequest)都用@Mock模拟,避免空指针异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 18:07:49