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

Mockito测试WebClient响应式方法:Jacoco覆盖率未识别问题

Jacoco未覆盖collectEndpointCount中getBearerToken后的逻辑,StepVerifier返回全null问题排查

问题详情

在为ResultsCollectorImpl的collect方法编写单元测试时遇到两个问题:

  1. collectEndpointCount方法中,调用getBearerToken()之后的代码逻辑未被Jacoco统计到覆盖率;
  2. 用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覆盖率截图

Jacoco覆盖率截图:getBearerToken后代码块无覆盖

问题原因及解决方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 18:05:08