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

如何使用Mockito与JUnit5测试包含CloseableHttpClient的checkFunction函数?

问题:Mockito+JUnit5测试HttpClient调用方法时覆盖率不足&测试方法不正确

我是Mockito与JUnit5的新手,目前尝试测试以下函数:

public boolean checkFunction(String element) {
 CloseableHttpClient client = HttpClients.createDefault();
 String uri = "any url im hitting";
 HttpPost httpPost = new HttpPost(uri);
 String json = element;
 StringEntity entity;
 try {
 entity = new StringEntity(json);
 httpPost.setEntity(entity);
 httpPost.setHeader("Accept", "application/json");
 httpPost.setHeader("Authorization", "any token");
 CloseableHttpResponse response = client.execute(httpPost);
 String responseBody = EntityUtils.toString(response.getEntity());
 client.close();
 if (responseBody.contains("any string i wanna check")) return true;
 } catch (Exception e) {
 return false;
 }
 return false;
}

我编写了如下测试代码,但无法获得完整的代码覆盖率,且认为当前测试方法并不正确:

@Test
public void testCheckFunction() throws Exception {
 when(mockClass.checkFunction(Mockito.anyString())).thenReturn(false);
 assertEquals(false, mockclass.checkFunction("dummy"));
}

恳请各位提供解决方案,谢谢!


解决方案

嘿,你这是踩了Mockito新手最常见的坑之一——你直接mock了被测试的类本身!这就相当于告诉Mockito:“别执行真实的checkFunction逻辑了,直接返回我指定的false就行”,自然完全跳过了方法内部的代码,覆盖率肯定上不去,测试也根本没验证到实际逻辑。

正确的思路应该是:mock方法里依赖的外部组件(也就是CloseableHttpClient这个HTTP客户端),让它返回我们预设的响应,这样就能完整覆盖checkFunction里的所有分支了。下面是具体的步骤和代码:

1. 先重构原代码,让依赖可替换

首先得把CloseableHttpClient的创建逻辑抽出来,这样测试时才能替换成mock对象。修改你的业务类:

public class YourService { // 假设这是checkFunction所在的类

    // 把HttpClient的创建单独抽成一个方法,方便测试时mock
    protected CloseableHttpClient createHttpClient() {
        return HttpClients.createDefault();
    }

    public boolean checkFunction(String element) {
        CloseableHttpClient client = createHttpClient(); // 调用抽出来的方法
        String uri = "any url im hitting";
        HttpPost httpPost = new HttpPost(uri);
        String json = element;
        StringEntity entity;
        try {
            entity = new StringEntity(json);
            httpPost.setEntity(entity);
            httpPost.setHeader("Accept", "application/json");
            httpPost.setHeader("Authorization", "any token");
            CloseableHttpResponse response = client.execute(httpPost);
            String responseBody = EntityUtils.toString(response.getEntity());
            if (responseBody.contains("any string i wanna check")) return true;
        } catch (Exception e) {
            return false;
        } finally {
            // 把close移到finally,确保无论是否异常都关闭连接,避免资源泄漏
            try {
                if (client != null) client.close();
            } catch (IOException e) {
                // 可根据需求记录日志或处理异常
            }
        }
        return false;
    }
}

2. 编写正确的JUnit5+Mockito测试

现在我们可以mockCloseableHttpClient、CloseableHttpResponse和响应实体,覆盖所有代码分支:

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
public class YourServiceTest {

    @Mock
    private CloseableHttpClient mockHttpClient;
    @Mock
    private CloseableHttpResponse mockResponse;
    @Mock
    private HttpEntity mockHttpEntity;

    // 测试分支1:响应体包含目标字符串,返回true
    @Test
    void testCheckFunction_ContainsTargetString_ReturnsTrue() throws Exception {
        // 准备测试数据
        String testElement = "test payload";
        String mockResponseBody = "sample response that includes any string i wanna check";

        // 部分mock业务类,替换createHttpClient方法返回我们的mock对象
        YourService service = spy(new YourService());
        doReturn(mockHttpClient).when(service).createHttpClient();

        // 模拟HTTP调用的各个环节
        when(mockHttpClient.execute(any(HttpPost.class))).thenReturn(mockResponse);
        when(mockResponse.getEntity()).thenReturn(mockHttpEntity);
        when(EntityUtils.toString(mockHttpEntity)).thenReturn(mockResponseBody);

        // 执行测试方法
        boolean result = service.checkFunction(testElement);

        // 验证结果和行为
        assertTrue(result);
        verify(mockHttpClient, times(1)).execute(any(HttpPost.class)); // 验证HTTP请求被执行
        verify(mockHttpClient, times(1)).close(); // 验证连接被关闭
    }

    // 测试分支2:响应体不包含目标字符串,返回false
    @Test
    void testCheckFunction_NoTargetString_ReturnsFalse() throws Exception {
        String testElement = "test payload";
        String mockResponseBody = "normal response without target content";

        YourService service = spy(new YourService());
        doReturn(mockHttpClient).when(service).createHttpClient();

        when(mockHttpClient.execute(any(HttpPost.class))).thenReturn(mockResponse);
        when(mockResponse.getEntity()).thenReturn(mockHttpEntity);
        when(EntityUtils.toString(mockHttpEntity)).thenReturn(mockResponseBody);

        boolean result = service.checkFunction(testElement);

        assertFalse(result);
        verify(mockHttpClient, times(1)).close();
    }

    // 测试分支3:调用HTTP接口抛出异常,返回false
    @Test
    void testCheckFunction_HttpCallThrowsException_ReturnsFalse() throws Exception {
        String testElement = "test payload";

        YourService service = spy(new YourService());
        doReturn(mockHttpClient).when(service).createHttpClient();

        // 模拟HTTP调用抛出异常
        when(mockHttpClient.execute(any(HttpPost.class))).thenThrow(new RuntimeException("Connection failed"));

        boolean result = service.checkFunction(testElement);

        assertFalse(result);
        verify(mockHttpClient, times(1)).close(); // 因为移到了finally,所以异常时也会关闭连接
    }
}

3. 几个关键要点

  • 不要mock被测试类本身:mock的是它依赖的外部服务(比如HTTP客户端),这样才能执行真实的业务逻辑
  • 用spy做部分mock:spy可以保留类的真实行为,只替换我们需要修改的方法(比如这里的createHttpClient)
  • 覆盖所有分支:要覆盖“返回true”、“返回false”、“抛出异常”三个分支,这样就能拿到100%的代码覆盖率
  • 资源要正确释放:把client.close()移到finally块,避免异常时资源泄漏,同时也能覆盖finally分支的代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 10:52:28