Spring Boot+Mockito:Mock RestTemplate时返回值始终为空的问题
Mockito测试RestTemplate返回空指针的问题解决
问题诊断
- RestTemplate未被正确Mock:服务类里的RestTemplate是通过
new RestTemplate(...)直接初始化的,不是依赖注入的实例,导致@InjectMocks无法替换掉原有的RestTemplate。测试时服务类还是用自己创建的真实RestTemplate,而非Mock对象,所以when(...).thenReturn(...)完全不生效,postForObject返回null触发空指针。 - postForObject参数匹配错误:原方法调用的是
restTemplate.postForObject(String, MultiValueMap, Class),但测试中第二个参数匹配的是any(Class.class),和实际传入的MultiValueMap类型不匹配,即使RestTemplate被正确Mock,这个匹配也不会触发。 - CaptchaResponse的Mock设置错误:用
mock(CaptchaResponse.class)创建的是Mock对象,直接调用setSuccess(false)不会改变其内部状态(Mock对象的方法默认无实际逻辑),需要用Mockito的Stubbing指定getSuccess()的返回值。
解决方案
步骤1:让RestTemplate可被Mock替换
推荐把服务类的RestTemplate改成构造注入,这样测试时@InjectMocks能自动替换成Mock实例:
@Service public class CaptchaValidatorServiceImpl implements CaptchaValidatorService{ private static final String GOOGLE_CAPTCHA_ENDPOINT = "someEndpoint"; private String stage; private String captchaSecret; private final RestTemplate restTemplate; // 构造注入RestTemplate public CaptchaValidatorServiceImpl() { // 保留原有初始化逻辑,或者改为注入外部Bean this.restTemplate = new RestTemplate(getClientHttpRequestFactory()); } // 或者如果用外部配置的RestTemplate Bean: // public CaptchaValidatorServiceImpl(RestTemplate restTemplate) { // this.restTemplate = restTemplate; // } @Override public void checkToken(String token) throws Exception{ // 原有逻辑不变 } private SimpleClientHttpRequestFactory getClientHttpRequestFactory(){ // 原有实现 } }
如果不想修改服务类构造,也可以用反射替换Spy对象里的RestTemplate字段,在测试类中添加:
@BeforeEach void setUp() throws NoSuchFieldException, IllegalAccessException { Field restTemplateField = CaptchaValidatorServiceImpl.class.getDeclaredField("restTemplate"); restTemplateField.setAccessible(true); restTemplateField.set(captchaValidatorService, restTemplate); }
步骤2:修正postForObject的参数匹配
测试中参数要和实际调用匹配,第二个参数是MultiValueMap,所以调整匹配规则:
Mockito.when(restTemplate.postForObject( Mockito.anyString(), ArgumentMatchers.any(MultiValueMap.class), ArgumentMatchers.eq(CaptchaResponse.class) )).thenReturn(captchaResponse);
或者用更宽松的匹配(适合不关心请求体内容的场景):
Mockito.when(restTemplate.postForObject( Mockito.anyString(), ArgumentMatchers.any(), ArgumentMatchers.eq(CaptchaResponse.class) )).thenReturn(captchaResponse);
步骤3:正确设置CaptchaResponse的返回值
因为是Mock对象,直接调用setSuccess无效,需要指定getSuccess()的返回值:
// 替换原有的captchaResponse.setSuccess(false); Mockito.when(captchaResponse.getSuccess()).thenReturn(false);
如果CaptchaResponse是简单POJO,也可以直接用真实对象代替Mock:
private CaptchaResponse captchaResponse = new CaptchaResponse(); // 直接设置属性 captchaResponse.setSuccess(false);
完整修正后的测试类
@SpringBootTest public class CaptchaValidatorTest{ @Mock private RestTemplate restTemplate; @InjectMocks private CaptchaValidatorServiceImpl captchaValidatorService; private CaptchaResponse captchaResponse = mock(CaptchaResponse.class); @BeforeEach void setUp() throws NoSuchFieldException, IllegalAccessException { // 反射替换服务类中的RestTemplate(如果没改构造注入的话) Field restTemplateField = CaptchaValidatorServiceImpl.class.getDeclaredField("restTemplate"); restTemplateField.setAccessible(true); restTemplateField.set(captchaValidatorService, restTemplate); } @Test public void shouldThrowInvalidTokenException() { // 设置CaptchaResponse的getSuccess返回false Mockito.when(captchaResponse.getSuccess()).thenReturn(false); // 正确匹配postForObject参数 Mockito.when(restTemplate.postForObject( Mockito.anyString(), ArgumentMatchers.any(MultiValueMap.class), ArgumentMatchers.eq(CaptchaResponse.class) )).thenReturn(captchaResponse); Exception exception = assertThrows(InvalidCaptchaTokenException.class, () -> captchaValidatorService.checkToken("test")); assertEquals("Invalid Token", exception.getMessage()); } }
内容的提问来源于stack exchange,提问作者Rafael Picolo
相关产品推荐
相关产品推荐

