如何用Mockito模拟带ResponseHandler的Apache HTTPClient以测试服务?
没问题,我来一步步教你怎么用Mockito模拟这段带自定义ResponseHandler的Apache HttpClient调用!
首先,先明确我们的目标:测试你自己的服务类,里面调用了httpClient.execute(httpGet, responseHandler),其中ResponseHandler是你用lambda写的自定义逻辑。我们需要模拟HttpClient的行为,让它触发这个handler并返回我们预设的结果,同时验证整个流程的正确性。
先看一下被测试的服务类示例(方便上下文理解)
假设你的服务类大概是这样的:
import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.ResponseHandler; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyApiService { private static final Logger log = LoggerFactory.getLogger(MyApiService.class); private final HttpClient httpClient; // 构造注入HttpClient,方便测试时替换为Mock对象 public MyApiService(HttpClient httpClient) { this.httpClient = httpClient; } public String fetchData(String apiUrl) throws Exception { HttpGet httpGet = new HttpGet(apiUrl); // 你自定义的ResponseHandler ResponseHandler<String> responseHandler = response -> { int status = response.getStatusLine().getStatusCode(); if (status == HttpStatus.SC_OK) { return EntityUtils.toString(response.getEntity()); } else { log.error("Accessing API returned error ..."); // 这里原代码没写返回值,我默认返回null,你可以根据实际情况调整 return null; } }; return httpClient.execute(httpGet, responseHandler); } }
测试类实现(Mockito + JUnit 5)
下面是完整的测试代码,我会在关键地方加注释说明:
import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.ResponseHandler; import org.apache.http.util.EntityUtils; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.io.ByteArrayInputStream; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class MyApiServiceTest { // Mock HttpClient对象,替换掉真实的HttpClient @Mock private HttpClient httpClient; // 把Mock的HttpClient注入到被测试的服务类中 @InjectMocks private MyApiService myApiService; @Test void fetchData_WhenOkResponse_ReturnsExpectedBody() throws Exception { // 1. 准备我们需要的Mock对象和预设数据 String expectedResponseBody = "Mocked API Response Content"; HttpResponse mockResponse = mock(HttpResponse.class); StatusLine mockStatusLine = mock(StatusLine.class); HttpEntity mockEntity = mock(HttpEntity.class); // 预设状态码为200 OK when(mockStatusLine.getStatusCode()).thenReturn(HttpStatus.SC_OK); when(mockResponse.getStatusLine()).thenReturn(mockStatusLine); // 用Mockito的mockStatic来模拟EntityUtils.toString方法(因为它是静态方法) try (var mockedEntityUtils = mockStatic(EntityUtils.class)) { mockedEntityUtils.when(() -> EntityUtils.toString(mockEntity)) .thenReturn(expectedResponseBody); // 2. 核心:设定HttpClient.execute的行为 // 这里不能直接thenReturn,因为我们需要触发传入的ResponseHandler逻辑 // 用thenAnswer获取传入的handler,手动调用它的handleResponse方法 when(httpClient.execute(any(HttpGet.class), any(ResponseHandler.class))) .thenAnswer(invocation -> { // 获取第二个参数:我们自定义的ResponseHandler ResponseHandler<String> handler = invocation.getArgument(1); // 调用handler的handleResponse,传入我们预设的mockResponse return handler.handleResponse(mockResponse); }); // 3. 执行被测试的方法 String result = myApiService.fetchData("https://test-api.com/data"); // 4. 验证结果和行为是否符合预期 assertEquals(expectedResponseBody, result); // 验证HttpClient的execute方法被调用了一次 verify(httpClient, times(1)).execute(any(HttpGet.class), any(ResponseHandler.class)); // 验证EntityUtils.toString被调用了一次 mockedEntityUtils.verify(() -> EntityUtils.toString(mockEntity), times(1)); } } @Test void fetchData_WhenNonOkResponse_LogsErrorAndReturnsNull() throws Exception { // 1. 准备非200的Mock响应 HttpResponse mockResponse = mock(HttpResponse.class); StatusLine mockStatusLine = mock(StatusLine.class); when(mockStatusLine.getStatusCode()).thenReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR); when(mockResponse.getStatusLine()).thenReturn(mockStatusLine); // 2. 同样设定HttpClient.execute的行为 when(httpClient.execute(any(HttpGet.class), any(ResponseHandler.class))) .thenAnswer(invocation -> { ResponseHandler<String> handler = invocation.getArgument(1); return handler.handleResponse(mockResponse); }); // 3. 执行测试方法 String result = myApiService.fetchData("https://test-api.com/data"); // 4. 验证结果 assertNull(result); verify(httpClient, times(1)).execute(any(HttpGet.class), any(ResponseHandler.class)); // 如果要验证日志是否正确打印,可以用slf4j-test库 // 比如添加依赖后,用LoggerAssert.assertThat(log).hasLogged(Level.ERROR, "Accessing API returned error ..."); } }
关键知识点说明
- thenAnswer的用法:这是测试这种场景的核心,因为我们需要让传入的ResponseHandler实际执行,而不是直接返回一个固定值。通过thenAnswer可以拿到方法调用时传入的参数,手动触发handler的逻辑。
- 静态方法Mock:因为你的handler里用到了
EntityUtils.toString这个静态方法,所以需要用Mockito的mockStatic(要求Mockito 3.4+版本)来模拟它的返回值,避免实际调用时出现IO异常。 - 日志验证:如果需要验证错误日志是否打印,可以引入
slf4j-test库,它能捕获日志输出,方便做断言。
内容的提问来源于stack exchange,提问作者Zmaja
相关产品推荐
相关产品推荐

