Spring Boot中RestTemplate Mock服务响应测试返回null问题求助
Spring Boot中Mock RestTemplate模拟服务响应失败问题
问题描述
测试Spring Boot的PricingService时,无法通过Mock RestTemplate模拟服务响应,调用getPricing方法始终返回null。
服务代码
@Service public class PricingServiceImpl implements PricingService { private static final String URL = "http://127.0.0.1:4000/pricing?countryCode="; @Autowired RestTemplate restTemplate; @Override public ResponseEntity<BigDecimal> getPricing(String countryCode) { return restTemplate.exchange(URL + countryCode, HttpMethod.GET, null, new ParameterizedTypeReference<BigDecimal>() { }); } }
初始测试代码
@ExtendWith(MockitoExtension.class) @RunWith(SpringRunner.class) public class PricingServiceImplTest { @InjectMocks private PricingService pricingService = new PricingServiceImpl(); @Mock private RestTemplate restTemplate; @Before public void setUp() { PricingResponse pricingResponse = new PricingResponse(); pricingResponse.setPrice(BigDecimal.TEN); Mockito.when(restTemplate.exchange(any(), eq(HttpMethod.GET), any(), any(ParameterizedTypeReference.class))).thenReturn(new ResponseEntity<>(pricingResponse, HttpStatus.OK)); } @Test public void test() { ResponseEntity<BigDecimal> result = pricingService.getPricing("NL"); Assertions.assertNotNull(result); } }
编辑后的测试代码(改用Setter注入仍无效)
@RunWith(MockitoJUnitRunner.class) public class PricingServiceImplTest { private PricingServiceImpl pricingService = new PricingServiceImpl(); @Mock private RestTemplate restTemplate; @Before public void setUp() { PricingResponse pricingResponse = new PricingResponse(); pricingResponse.setPrice(BigDecimal.TEN); Mockito.when(restTemplate.exchange(any(), eq(HttpMethod.GET), any(), any(ParameterizedTypeReference.class))).thenReturn(new ResponseEntity<>(pricingResponse, HttpStatus.OK)); } @Test public void test() { pricingService.setRestTemplate(restTemplate); ResponseEntity<BigDecimal> result = pricingService.getPricing("NL"); Assertions.assertNotNull(result); } }
问题原因分析
- 测试框架注解冲突:初始测试类同时使用JUnit5的
@ExtendWith(MockitoExtension.class)和JUnit4的@RunWith(SpringRunner.class),导致Mock对象注入逻辑异常。 - 返回类型不匹配:服务中
restTemplate.exchange期望返回ResponseEntity<BigDecimal>,但测试中Mock返回的是ResponseEntity<PricingResponse>,类型不匹配导致Stub规则不生效,实际调用返回null。 - ParameterizedTypeReference匹配失效:匿名实现的
ParameterizedTypeReference使用any(ParameterizedTypeReference.class)无法精准匹配,Mockito无法识别对应的Stub规则。
解决方案
方案1:修正测试框架注解与Stub类型(服务代码保持不变)
统一使用JUnit4或JUnit5的注解,同时确保Mock返回类型与服务期望一致:
JUnit5版本测试代码
@ExtendWith(MockitoExtension.class) public class PricingServiceImplTest { @InjectMocks private PricingServiceImpl pricingService; @Mock private RestTemplate restTemplate; @BeforeEach public void setUp() { // 匹配服务中使用的ParameterizedTypeReference实例 ParameterizedTypeReference<BigDecimal> targetType = new ParameterizedTypeReference<BigDecimal>() {}; // 返回与服务期望一致的ResponseEntity<BigDecimal> Mockito.when(restTemplate.exchange( anyString(), eq(HttpMethod.GET), isNull(), eq(targetType) )).thenReturn(new ResponseEntity<>(BigDecimal.TEN, HttpStatus.OK)); } @Test public void testGetPricing() { ResponseEntity<BigDecimal> result = pricingService.getPricing("NL"); Assertions.assertNotNull(result); Assertions.assertEquals(BigDecimal.TEN, result.getBody()); } }
JUnit4版本测试代码
@RunWith(MockitoJUnitRunner.class) public class PricingServiceImplTest { private PricingServiceImpl pricingService = new PricingServiceImpl(); @Mock private RestTemplate restTemplate; @Before public void setUp() { ParameterizedTypeReference<BigDecimal> targetType = new ParameterizedTypeReference<BigDecimal>() {}; Mockito.when(restTemplate.exchange( anyString(), eq(HttpMethod.GET), isNull(), eq(targetType) )).thenReturn(new ResponseEntity<>(BigDecimal.TEN, HttpStatus.OK)); } @Test public void test() { pricingService.setRestTemplate(restTemplate); ResponseEntity<BigDecimal> result = pricingService.getPricing("NL"); Assertions.assertNotNull(result); } }
方案2:调整服务代码返回PricingResponse(若业务实际需要)
如果接口实际应返回PricingResponse而非直接返回BigDecimal,修改服务代码后再同步调整测试:
修改后的服务代码
@Service public class PricingServiceImpl implements PricingService { private static final String URL = "http://127.0.0.1:4000/pricing?countryCode="; @Autowired RestTemplate restTemplate; @Override public ResponseEntity<PricingResponse> getPricing(String countryCode) { return restTemplate.exchange(URL + countryCode, HttpMethod.GET, null, new ParameterizedTypeReference<PricingResponse>() {}); } }
对应测试代码
@RunWith(MockitoJUnitRunner.class) public class PricingServiceImplTest { private PricingServiceImpl pricingService = new PricingServiceImpl(); @Mock private RestTemplate restTemplate; @Before public void setUp() { PricingResponse pricingResponse = new PricingResponse(); pricingResponse.setPrice(BigDecimal.TEN); ParameterizedTypeReference<PricingResponse> targetType = new ParameterizedTypeReference<PricingResponse>() {}; Mockito.when(restTemplate.exchange( anyString(), eq(HttpMethod.GET), isNull(), eq(targetType) )).thenReturn(new ResponseEntity<>(pricingResponse, HttpStatus.OK)); } @Test public void test() { pricingService.setRestTemplate(restTemplate); ResponseEntity<PricingResponse> result = pricingService.getPricing("NL"); Assertions.assertNotNull(result); Assertions.assertEquals(BigDecimal.TEN, result.getBody().getPrice()); } }
内容的提问来源于stack exchange,提问作者noloman
相关产品推荐
相关产品推荐

