如何编写JUnit测试覆盖FutureCallback<HttpResponse>三类回调场景
解决方案
核心思路
CloseableHttpAsyncClient是Spring注入的依赖,我们可以通过Mockito捕获调用execute方法时传入的FutureCallback实例,主动触发三个回调方法,即可覆盖对应的代码分支。
具体实现步骤
1. 引入基础测试依赖
如果项目还没有引入相关测试依赖,可补充如下配置(以Maven为例):
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-junit-jupiter</artifactId> <version>4.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> </dependency>
2. 编写单元测试类
import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.concurrent.FutureCallback; import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.core.env.Environment; import java.io.ByteArrayInputStream; import java.lang.reflect.Method; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class SplunkLoggerTest { @Mock private Environment environment; @Mock private CloseableHttpAsyncClient httpClient; @InjectMocks private SplunkLogger splunkLogger; // 用于捕获execute方法传入的FutureCallback实例 private final ArgumentCaptor<FutureCallback<HttpResponse>> callbackCaptor = ArgumentCaptor.forClass(FutureCallback.class); // 通用前置配置:设置非local环境,才会走到HTTP请求逻辑 private void initNonLocalEnv() { when(environment.getActiveProfiles()).thenReturn(new String[]{"dev"}); } // 反射调用private的log方法,也可以将log调整为包私有省略反射逻辑 private void invokeLogMethod(SplunkMessage message) throws Exception { Method logMethod = SplunkLogger.class.getDeclaredMethod("log", SplunkMessage.class); logMethod.setAccessible(true); logMethod.invoke(splunkLogger, message); } @Test public void testCompletedCallback() throws Exception { initNonLocalEnv(); SplunkMessage testMessage = new SplunkMessage(); // 构造模拟返回的响应对象 HttpResponse mockResponse = mock(HttpResponse.class); HttpEntity mockEntity = mock(HttpEntity.class); when(mockResponse.getEntity()).thenReturn(mockEntity); when(mockEntity.getContent()).thenReturn(new ByteArrayInputStream("splunk返回成功".getBytes())); invokeLogMethod(testMessage); // 捕获回调实例并主动触发completed方法 verify(httpClient).execute(any(HttpPost.class), callbackCaptor.capture()); callbackCaptor.getValue().completed(mockResponse); } @Test public void testFailedCallback() throws Exception { initNonLocalEnv(); SplunkMessage testMessage = new SplunkMessage(); invokeLogMethod(testMessage); // 捕获回调实例并主动触发failed方法 verify(httpClient).execute(any(HttpPost.class), callbackCaptor.capture()); callbackCaptor.getValue().failed(new RuntimeException("请求异常模拟")); } @Test public void testCancelledCallback() throws Exception { initNonLocalEnv(); SplunkMessage testMessage = new SplunkMessage(); invokeLogMethod(testMessage); // 捕获回调实例并主动触发cancelled方法 verify(httpClient).execute(any(HttpPost.class), callbackCaptor.capture()); callbackCaptor.getValue().cancelled(); } }
注意事项
- 若不想用反射调用private方法,可将
log方法的访问修饰符调整为包私有,同包下的测试类可直接调用无需反射。 - 上述测试执行后,FutureCallback的三个方法逻辑都会被触发,覆盖率统计工具会对应标记这三个分支为已覆盖。
内容的提问来源于stack exchange,提问作者kumar001
相关产品推荐
相关产品推荐

