Spring Boot中Mockito模拟同一函数内两次HttpClient调用失败求助
Mockito模拟HttpClient重试请求失效问题解决及代码优化
问题原因及解决
你当前的Mock失效核心原因是:业务代码在方法内部直接创建HttpClient实例(HttpClientBuilder.create().build()),导致你Mock的mockedCloseableHttpClient对象根本没有被业务代码使用。不管是第一次还是第二次请求,用的都是方法内新创建的真实HttpClient,你的Mock配置完全无法作用到这些实例上。
解决步骤
重构业务代码,通过依赖注入提供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 ""; } }编写正确的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()); } }
代码逻辑优化建议
修复重试逻辑
原代码的重试逻辑错误,第二次请求会无条件执行,必须将其放到第一次请求的catch块内,确保只有第一次失败时才触发重试。资源泄漏防护
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); } } }提取重复代码
两次请求的创建、执行逻辑高度重复,可提取为私有方法,减少冗余,提高维护性。异常处理升级
不要仅打印异常,应根据业务需求抛出异常或返回错误标识,让上层调用感知失败状态;避免使用空的catch块。配置中心化
将请求URI等配置参数提取为常量或配置文件,避免硬编码。
内容的提问来源于stack exchange,提问作者devumesh
相关产品推荐
相关产品推荐

