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

Mock RestTemplate.postForEntity时为何报‘URI is not absolute’错误?

问题:Mock RestTemplate postForEntity时出现“URI is not absolute”错误

我想Mock所属类中RestTemplate实例变量的postForEntity方法,但即便参考示例传入anyString()作为URI,仍持续抛出java.lang.IllegalArgumentException: URI is not absolute错误。需要说明的是,我没有用@Mock注解RestTemplate,因为要测试它是否被类的构造器正确构建(主要是超时配置)。

被测类大致代码如下:

public class MyClient {
    ...
    @Getter
    @Setter
    private RestTemplate restTemplate = new RestTemplate();

    public MyClient(Property x, Property y, Property z){
        this.x = x; 
        this.y = y;
        this.z = z; 
    }

    // 要测试的新构造器
    public MyClient(Property x, Property y, Property z, int timeout){
        this(x, y, z);
        this.restTemplate =((SimpleClientHttpRequestFactory)getRestTemplate().getRequestFactory()).setReadTimeout(timeout);
    }
    
    public void makeRequests() {
        ...
        // myEndpoint、myRequest已提前赋值,CustomResponse是自定义类
        ResponseEntity resEntity = getRestTemplate().postForEntity(myEndpoint, myRequest, CustomResponse.class);
    }
}

测试类代码:

@RunWith(MockitoJUnitRunner.class)
public class MyClassTest extends TestCase{
    ...
    
    @Test
    public void test_new_constructor(){
        MyClient myClient = new MyClient(x, y, z, 1000);
        RestTemplate restTemplate = myClient.getRestTemplate(); 
        Mockito.when(restTemplate.postForEntity(anyString(), any(), any(Class.class))).thenAnswer(new AnswersWithDelay(100000, new Returns(new ResponseEntity(CustomResponse, HttpStatus.OK)) ));
        // 本来打算加延迟触发超时并校验异常,但还没到这一步就报错了
        myClient.makeRequests();
    }
}

疑问:

  1. 报错的原因是什么?
  2. 测试实际RestTemplate是不是错误做法?
  3. 我原本以为用Mockito.when处理网络调用能避免依赖真实端点,但实际并非如此吗?

解答

报错原因

你现在是直接对真实的RestTemplate实例调用Mockito.when(),而不是Mock对象。Mockito的when()方法默认会先执行一次原方法(也就是真实的postForEntity),这时候anyString()会被解析成一个非绝对URI的占位符,触发RestTemplate内部的URI合法性校验,所以抛出了“URI is not absolute”错误。

关于测试真实RestTemplate的合理性

直接测试真实RestTemplate不是错误做法,但你的实现方式不对:你想用Mockito拦截真实对象的方法调用,这需要用Mockito的Spy功能,而不是直接对真实对象用when()。

为什么没避免真实调用?

你以为Mockito.when能避免真实调用,那是针对@Mock创建的完全假的对象。对真实对象用when(),默认会先执行原方法逻辑,所以才会触发真实的URI校验,甚至可能发起真实请求。

修正方案

要同时保留RestTemplate的真实配置(比如你要测试的超时),又能拦截postForEntity的调用,需要把真实RestTemplate包装成Spy对象,并用doAnswer()来设置行为(避免先执行原方法):

@Test
public void test_new_constructor(){
    MyClient myClient = new MyClient(x, y, z, 1000);
    // 把真实RestTemplate包装成Spy
    RestTemplate restTemplateSpy = Mockito.spy(myClient.getRestTemplate()); 
    // 用doAnswer()设置延迟返回,避免先执行原方法
    Mockito.doAnswer(new AnswersWithDelay(100000, new Returns(new ResponseEntity<>(new CustomResponse(), HttpStatus.OK))))
           .when(restTemplateSpy)
           .postForEntity(anyString(), any(), any(Class.class));
    // 将Spy对象设置回MyClient
    myClient.setRestTemplate(restTemplateSpy);
    // 执行方法,此时会触发超时逻辑
    try {
        myClient.makeRequests();
        // 如果没抛出超时异常,测试失败
        fail("Expected timeout exception");
    } catch (ResourceAccessException e) {
        // 确认是超时导致的异常
        assertTrue(e.getCause() instanceof SocketTimeoutException);
    }
}

补充:被测类构造器的小问题

你新构造器里的代码有bug:setReadTimeout是void方法,不能直接赋值给restTemplate,正确写法应该是:

public MyClient(Property x, Property y, Property z, int timeout){
    this(x, y, z);
    SimpleClientHttpRequestFactory factory = (SimpleClientHttpRequestFactory) getRestTemplate().getRequestFactory();
    factory.setReadTimeout(timeout);
    // 不需要重新赋值restTemplate,因为factory是引用类型,修改属性会直接生效
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 13:03:14