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

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);
    }
}

问题原因分析

  1. 测试框架注解冲突:初始测试类同时使用JUnit5的@ExtendWith(MockitoExtension.class)和JUnit4的@RunWith(SpringRunner.class),导致Mock对象注入逻辑异常。
  2. 返回类型不匹配:服务中restTemplate.exchange期望返回ResponseEntity<BigDecimal>,但测试中Mock返回的是ResponseEntity<PricingResponse>,类型不匹配导致Stub规则不生效,实际调用返回null。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 00:20:32