JUnit5+Mockito模拟外部API调用时RestTemplate.exchange打桩返回空问题
问题根源与解决方案
你的问题出在两个核心点上:
- 方法内部直接
new RestTemplate(),Mockito无法模拟这种局部创建的对象——它只能拦截被注入或传入的实例。 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
相关产品推荐
相关产品推荐

