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

Spring Boot中Mockito模拟同一函数内两次HttpClient调用失败求助

Mockito模拟HttpClient重试请求失效问题解决及代码优化

问题原因及解决

你当前的Mock失效核心原因是:业务代码在方法内部直接创建HttpClient实例(HttpClientBuilder.create().build()),导致你Mock的mockedCloseableHttpClient对象根本没有被业务代码使用。不管是第一次还是第二次请求,用的都是方法内新创建的真实HttpClient,你的Mock配置完全无法作用到这些实例上。

解决步骤

  1. 重构业务代码,通过依赖注入提供HttpClient
    修改Someclass,将HttpClient的创建从方法内移到构造函数,同时保留无参构造兼容原有调用:

    class Someclass {
        private final HttpClient httpClient;
        // 假设uri1、uri2为类内常量或传入参数
        private static final String URI1 = "uri1";
        private static final String URI2 = "uri2";
    
        // 构造注入,测试时传入Mock对象
        public Someclass(HttpClient httpClient) {
            this.httpClient = httpClient;
        }
    
        // 兼容原有调用的无参构造
        public Someclass() {
            this(HttpClientBuilder.create().build());
        }
    
        public String funcA(String s) {
            // 修正重试逻辑:只有第一次失败才执行第二次请求
            try {
                HttpGet httpget = new HttpGet(URI1);
                HttpResponse httpresponse = httpClient.execute(httpget);
                // some logic
            } catch (Exception err) {
                System.out.println(err);
                // 重试逻辑放到catch块内
                try {
                    HttpGet httpget = new HttpGet(URI2);
                    HttpResponse httpresponse = httpClient.execute(httpget);
                    // some logic
                } catch (Exception err2) {
                    System.out.println(err2);
                }
            }
            // 根据业务逻辑补充返回值
            return "";
        }
    }
    
  2. 编写正确的Mock测试代码
    利用构造注入传入Mock的HttpClient,通过Mockito的thenThrow+thenReturn实现“第一次调用失败,第二次调用返回Mock响应”的逻辑:

    @ExtendWith(MockitoExtension.class)
    class SomeclassTest {
        @Mock
        private CloseableHttpClient mockedHttpClient;
        @Mock
        private CloseableHttpResponse mockedResponse;
        @Mock
        private HttpEntity mockedEntity;
    
        private Someclass someclass;
    
        @BeforeEach
        void setUp() {
            // 注入Mock的HttpClient
            someclass = new Someclass(mockedHttpClient);
        }
    
        @Test
        void testFuncAFirstCallFailsThenRetrySucceeds() throws IOException {
            // 第一次调用抛出异常,第二次返回Mock响应
            when(mockedHttpClient.execute(any(HttpGet.class)))
                    .thenThrow(new IOException("First request failed"))
                    .thenReturn(mockedResponse);
    
            // 配置Mock响应的内容
            when(mockedResponse.getEntity()).thenReturn(mockedEntity);
            when(mockedEntity.getContent()).thenReturn(new ByteArrayInputStream("Success".getBytes()));
    
            // 执行测试方法
            someclass.funcA("test");
    
            // 验证调用次数:总共两次execute调用
            verify(mockedHttpClient, times(2)).execute(any(HttpGet.class));
    
            // 验证两次请求的URI是否正确
            ArgumentCaptor<HttpGet> requestCaptor = ArgumentCaptor.forClass(HttpGet.class);
            verify(mockedHttpClient, times(2)).execute(requestCaptor.capture());
            List<HttpGet> requests = requestCaptor.getAllValues();
            assertEquals(URI1, requests.get(0).getURI().toString());
            assertEquals(URI2, requests.get(1).getURI().toString());
        }
    }
    

代码逻辑优化建议

  1. 修复重试逻辑
    原代码的重试逻辑错误,第二次请求会无条件执行,必须将其放到第一次请求的catch块内,确保只有第一次失败时才触发重试。

  2. 资源泄漏防护
    HttpClient的请求、响应、实体流都需要手动关闭,建议使用try-with-resources语句自动管理资源,避免内存泄漏:

    private String executeRequest(String uri) throws IOException {
        try (HttpGet httpGet = new HttpGet(uri);
             CloseableHttpResponse response = (CloseableHttpResponse) httpClient.execute(httpGet)) {
            HttpEntity entity = response.getEntity();
            if (entity == null) {
                throw new IOException("Empty response");
            }
            try (InputStream inputStream = entity.getContent()) {
                // 处理响应内容
                return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
            }
        }
    }
    
  3. 提取重复代码
    两次请求的创建、执行逻辑高度重复,可提取为私有方法,减少冗余,提高维护性。

  4. 异常处理升级
    不要仅打印异常,应根据业务需求抛出异常或返回错误标识,让上层调用感知失败状态;避免使用空的catch块。

  5. 配置中心化
    将请求URI等配置参数提取为常量或配置文件,避免硬编码。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 11:25:30