Mockito.when为自定义RestTemplate打桩测试报错的解决方法
问题根因
两个报错的核心原因如下:
MissingMethodInvocationException:你声明的@Mock RestTemplate _restTemplate根本没有被业务代码调用。业务类中getRestTemplate()是方法内部直接new全新的RestTemplate实例,既没有引用测试类中的mock对象,你注释掉的when(_accountService.getRestTemplate())写法本身也不符合Mockito规则:_accountService是@InjectMocks创建的真实业务对象,不是Mock/Spy包装的对象,直接对真实对象方法做when打桩会先执行真实逻辑,无法生效。- 配置项为null导致URL拼接异常:测试类中没有将代码里的
env(Spring Environment对象)声明为Mock对象,也没有初始化对应的Mock实例,调用env.getProperty()拿不到预设值,拼接出的URL就是/null/Test/null格式,触发真实HTTP请求后报IO错误。 - 额外隐藏问题:测试类没有加Mockito启动注解(JUnit5加
@ExtendWith(MockitoExtension.class),JUnit4加@RunWith(MockitoJUnitRunner.class)),所有@Mock/@InjectMocks注解都不会生效,对象默认是null;打桩exchange方法时无参any()容易出现泛型匹配失败,导致打桩不生效。
最小改动修复方案(不修改业务代码)
直接调整测试类写法即可:
- 给测试类加上Mockito运行注解,补全所有业务类依赖的Mock声明
- 将被测试类
AccountService声明为@Spy,允许打桩它的getRestTemplate()方法返回mock的RestTemplate实例,注意打桩Spy方法要用doReturn语法避免执行真实初始化逻辑 - 修正参数匹配逻辑,保证预期URL和业务代码拼接逻辑完全一致
完整测试代码示例:
// JUnit5用这个注解,JUnit4替换成@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class) public class AccountServiceTest { @Spy @InjectMocks private AccountService _accountService; @Mock private RestTemplate _restTemplate; @Mock private Environment env; // 补全业务类中注入的其他依赖,避免实例化时空指针 @Mock private PoolingHttpClientConnectionManager poolingHttpClientConnectionManager; @Mock private ClientHttpRequestInterceptor tracingClientHttpRequestInterceptor; @Mock private ClientHttpRequestInterceptor traceLogClientHttpRequestInterceptor; // 测试固定值 private final String _accId = "123456"; private final TestRequest _resp = new TestRequest(); @Test public void shouldGetAccount_200() { // 打桩getRestTemplate,返回mock实例,不要执行真实的RestTemplate初始化逻辑 doReturn(_restTemplate).when(_accountService).getRestTemplate(); // 打桩配置项 when(env.getProperty("test1")).thenReturn("http://api.test.com"); when(env.getProperty("test2")).thenReturn("v1"); // 按照业务代码的拼接逻辑构造预期URL String expectedUrl = String.format("%s/Test/%s/%s", env.getProperty("test1"), env.getProperty("test2"), _accId); // 打桩exchange方法,指定HttpEntity类型避免泛型匹配失败 when(_restTemplate.exchange( eq(expectedUrl), eq(HttpMethod.GET), any(HttpEntity.class), eq(TestRequest.class) )).thenReturn(new ResponseEntity<>(_resp, HttpStatus.OK)); // 执行测试 TestRequest result = _accountService.getAcc(_accId, "testSession", "testKey", "testUser"); // 断言 assertThat(result).isEqualTo(_resp); } }
更优的长期方案(推荐重构业务代码)
RestTemplate本身是线程安全的,不需要每次调用接口都new实例,重复创建连接池、RestTemplate对象会造成不必要的资源浪费。建议把RestTemplate初始化逻辑移到Spring配置类中,作为全局Bean注入到Service里:
- 新建配置类初始化RestTemplate Bean,原来
getRestTemplate()里的逻辑全部移到配置类中,服务启动时只初始化一次 - AccountService中直接通过构造器注入RestTemplate、Environment等依赖,去掉内部的
getRestTemplate()方法
重构后测试类不需要再用@Spy打桩方法,直接@InjectMocks就能自动注入Mock的RestTemplate,测试逻辑更简洁,也符合Spring依赖注入的最佳实践。
内容的提问来源于stack exchange,提问作者lukaszFD
相关产品推荐
相关产品推荐

