如何使用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
相关产品推荐
相关产品推荐

