Mockito测试WebClient响应式方法:Jacoco覆盖率未识别问题
Jacoco未覆盖collectEndpointCount中getBearerToken后的逻辑,StepVerifier返回全null问题排查
问题详情
在为ResultsCollectorImpl的collect方法编写单元测试时遇到两个问题:
collectEndpointCount方法中,调用getBearerToken()之后的代码逻辑未被Jacoco统计到覆盖率;- 用
StepVerifier验证流程时,能确认流已完成,但最终返回的合并结果全为null。
collect方法核心逻辑
- 接收
timeRange参数,为两个不同端点分别调用collectEndpointCount启动异步操作; - 通过
extractAccessToken和getBearerToken获取Apigee访问令牌完成认证; - 用令牌构造请求URL,调用外部API获取端点使用次数数据;
- 经
processEndpointResponse处理响应,提取有效次数; - 最后通过
Mono.zip合并两个端点的结果返回。
代码片段
ResultsCollectorImpl.java
@Service public class ResultsCollectorImpl implements ResultsCollector { private final WebClient apigeeWebClient; private final TokenService tokenService; public ResultsCollectorImpl(WebClient apigeeWebClient, TokenService tokenService) { this.apigeeWebClient = apigeeWebClient; this.tokenService = tokenService; } @Override public Mono<ResultDto> collect(TimeRange timeRange) { Mono<Integer> endpoint1Count = collectEndpointCount("endpoint1", timeRange); Mono<Integer> endpoint2Count = collectEndpointCount("endpoint2", timeRange); return Mono.zip(endpoint1Count, endpoint2Count) .map(tuple -> new ResultDto(tuple.getT1(), tuple.getT2())); } private Mono<Integer> collectEndpointCount(String endpoint, TimeRange timeRange) { return getBearerToken() .map(token -> buildRequestUrl(endpoint, timeRange, token)) .flatMap(url -> apigeeWebClient.get() .uri(url) .retrieve() .bodyToMono(String.class)) .map(response -> processEndpointResponse(response)); } private Mono<String> getBearerToken() { return tokenService.getToken() .map(this::extractAccessToken); } private String extractAccessToken(Token token) { return token.getAccessToken(); } private String buildRequestUrl(String endpoint, TimeRange timeRange, String token) { return String.format("/api/%s?range=%s&token=%s", endpoint, timeRange, token); } private Integer processEndpointResponse(String response) { return Integer.parseInt(response.split(":")[1]); } }
ResultsCollectorImplTest.java
@ExtendWith(MockitoExtension.class) public class ResultsCollectorImplTest { @Mock private WebClient apigeeWebClient; @Mock private TokenService tokenService; @InjectMocks private ResultsCollectorImpl collector; @Test void testCollect() { TimeRange timeRange = new TimeRange("2024-01-01", "2024-01-02"); Token mockToken = new Token("mock-access-token"); when(tokenService.getToken()).thenReturn(Mono.just(mockToken)); StepVerifier.create(collector.collect(timeRange)) .expectNextMatches(result -> result.getEndpoint1Count() == null && result.getEndpoint2Count() == null) .verifyComplete(); } }
Jacoco覆盖率截图
问题原因及解决方案
1. getBearerToken()未正确Mock,导致后续流未触发
如果tokenService.getToken()返回Mono.empty()或错误流,collectEndpointCount中getBearerToken()之后的map、flatMap等操作都不会执行,自然没有覆盖率,且最终结果为null。
解决:
确保mock返回有效的令牌流:
when(tokenService.getToken()).thenReturn(Mono.just(new Token("valid-token")));
2. WebClient调用未正确Mock,外部API响应未返回
collectEndpointCount中调用外部API的逻辑如果没有正确mock,会导致flatMap内的WebClient请求没有返回数据,processEndpointResponse也不会执行。
解决:
构建完整的WebClient调用mock链,返回模拟响应:
WebClient.RequestHeadersUriSpec<?> mockUriSpec = mock(WebClient.RequestHeadersUriSpec.class); WebClient.ResponseSpec mockResponseSpec = mock(WebClient.ResponseSpec.class); when(apigeeWebClient.get()).thenReturn(mockUriSpec); when(mockUriSpec.uri(anyString())).thenReturn(mockUriSpec); when(mockUriSpec.retrieve()).thenReturn(mockResponseSpec); when(mockResponseSpec.bodyToMono(String.class)).thenReturn(Mono.just("count:100"));
3. StepVerifier未等待异步操作完成
如果流中存在延迟或异步操作未正确订阅,StepVerifier可能提前结束,导致后续逻辑没执行完。
解决:
确保使用verifyComplete()等待流完全终止,并验证预期结果:
StepVerifier.create(collector.collect(timeRange)) .expectNext(new ResultDto(100, 200)) .verifyComplete();
4. Jacoco配置问题
如果Jacoco未正确包含生产代码包,或者对Reactor异步代码的覆盖率统计需要特殊配置,也会导致部分逻辑未被统计。
解决:
检查Jacoco插件配置,确保includes包含生产代码的包路径:
<plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> <version>0.8.11</version> <configuration> <includes> <include>com/yourpackage/**/*.class</include> </includes> </configuration> </plugin>
内容的提问来源于stack exchange,提问作者DonutQuan
相关产品推荐
相关产品推荐

