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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:04:36