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

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()容易出现泛型匹配失败,导致打桩不生效。
最小改动修复方案(不修改业务代码)

直接调整测试类写法即可:

  1. 给测试类加上Mockito运行注解,补全所有业务类依赖的Mock声明
  2. 将被测试类AccountService声明为@Spy,允许打桩它的getRestTemplate()方法返回mock的RestTemplate实例,注意打桩Spy方法要用doReturn语法避免执行真实初始化逻辑
  3. 修正参数匹配逻辑,保证预期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里:

  1. 新建配置类初始化RestTemplate Bean,原来getRestTemplate()里的逻辑全部移到配置类中,服务启动时只初始化一次
  2. AccountService中直接通过构造器注入RestTemplate、Environment等依赖,去掉内部的getRestTemplate()方法
    重构后测试类不需要再用@Spy打桩方法,直接@InjectMocks就能自动注入Mock的RestTemplate,测试逻辑更简洁,也符合Spring依赖注入的最佳实践。

内容的提问来源于stack exchange,提问作者lukaszFD

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 16:45:14