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

如何对带UriBuilder的Spring WebClient做单元测试以满足代码覆盖率要求

Spring WebClient GET请求的单元测试与代码覆盖率优化

问题背景

我使用Spring WebClient编写了一个简洁的GET请求,代码如下:

webClient.get().uri(uriBuilder -> uriBuilder
            .queryParam("param1", "value1")
            .queryParam("param2", "value2")
            .path("/appPath")
            .build()
      ).retrieve().bodyToMono(MyClass.class).block();

目前我Mock UriBuilder函数后测试可成功通过,但代码覆盖率未达到SonarQube的要求,导致Jenkins构建失败。请问如何对该方法进行单元测试以满足代码覆盖率要求?

解决方案

要覆盖所有代码分支并满足SonarQube的覆盖率要求,需要完整覆盖WebClient调用链的每一段逻辑,包括lambda表达式里的UriBuilder配置、retrieve()/bodyToMono()/block()的调用流程。

1. 使用MockWebServer(推荐方案)

MockWebServer是Spring生态的工具,能模拟真实HTTP服务器,避免过度Mock,自然覆盖更多代码路径:

import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;

class WebClientRequestTest {
    private MockWebServer mockWebServer;
    private WebClient webClient;

    @BeforeEach
    void setUp() throws Exception {
        mockWebServer = new MockWebServer();
        mockWebServer.start();
        // 配置WebClient指向Mock服务器
        webClient = WebClient.builder()
                .baseUrl(mockWebServer.url("/").toString())
                .build();
    }

    @AfterEach
    void tearDown() throws Exception {
        mockWebServer.shutdown();
    }

    @Test
    void testGetRequest_FullCoverage() {
        // 模拟服务器返回预期JSON响应
        mockWebServer.enqueue(new MockResponse()
                .setBody("{\"id\":1,\"name\":\"test\"}")
                .addHeader("Content-Type", "application/json"));

        // 调用目标请求方法
        MyClass result = webClient.get().uri(uriBuilder -> uriBuilder
                        .queryParam("param1", "value1")
                        .queryParam("param2", "value2")
                        .path("/appPath")
                        .build())
                .retrieve()
                .bodyToMono(MyClass.class)
                .block();

        // 验证响应结果正确性
        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo(1);
        assertThat(result.getName()).isEqualTo("test");

        // 验证生成的URI是否符合预期(覆盖UriBuilder的所有配置逻辑)
        RecordedRequest recordedRequest = mockWebServer.takeRequest();
        assertThat(recordedRequest.getPath()).isEqualTo("/appPath?param1=value1&param2=value2");
    }
}

2. 优化Mockito Mock方式(若必须使用Mockito)

如果坚持用Mockito,需确保lambda里的UriBuilder方法被执行并验证,同时完整模拟WebClient调用链:

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.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.UriBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.function.Function;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class WebClientMockTest {
    @Mock
    private WebClient webClient;
    @Mock
    private WebClient.RequestHeadersUriSpec<?> requestSpec;
    @Mock
    private WebClient.ResponseSpec responseSpec;
    @Mock
    private UriBuilder uriBuilder;

    @Test
    void testGetRequest_CoverAllLogic() {
        // 模拟WebClient调用链
        when(webClient.get()).thenReturn(requestSpec);
        when(requestSpec.uri(any(Function.class))).thenReturn(requestSpec);
        when(requestSpec.retrieve()).thenReturn(responseSpec);
        when(responseSpec.bodyToMono(MyClass.class)).thenReturn(Mono.just(new MyClass(1, "test")));

        // 调用目标方法
        MyClass result = webClient.get().uri(builder -> builder
                        .queryParam("param1", "value1")
                        .queryParam("param2", "value2")
                        .path("/appPath")
                        .build())
                .retrieve()
                .bodyToMono(MyClass.class)
                .block();

        // 触发并验证lambda里的UriBuilder逻辑
        Function<UriBuilder, URI> uriFunction = (Function<UriBuilder, URI>) when(requestSpec.uri(any())).getArgument(0);
        // 模拟UriBuilder的链式调用
        when(uriBuilder.queryParam("param1", "value1")).thenReturn(uriBuilder);
        when(uriBuilder.queryParam("param2", "value2")).thenReturn(uriBuilder);
        when(uriBuilder.path("/appPath")).thenReturn(uriBuilder);
        when(uriBuilder.build()).thenReturn(URI.create("/appPath?param1=value1&param2=value2"));
        // 执行lambda
        uriFunction.apply(uriBuilder);

        // 验证所有方法都被调用
        verify(uriBuilder).queryParam("param1", "value1");
        verify(uriBuilder).queryParam("param2", "value2");
        verify(uriBuilder).path("/appPath");
        verify(uriBuilder).build();
        verify(webClient).get();
        verify(requestSpec).uri(any());
        verify(requestSpec).retrieve();
        verify(responseSpec).bodyToMono(MyClass.class);
    }
}

关键注意事项

  • 覆盖lambda逻辑:之前的Mock可能未触发UriBuilder的queryParam、path、build方法,导致这部分代码未被统计,必须确保lambda被执行并验证这些方法的调用。
  • 优先选择MockWebServer:这种方式更贴近真实请求场景,能自动覆盖WebClient内部的retrieve()、bodyToMono()等逻辑,无需手动Mock每个环节,更容易达到高覆盖率。
  • 验证请求细节:无论用哪种方式,都要验证生成的URI是否符合预期,确保UriBuilder的配置逻辑被正确执行。

内容的提问来源于stack exchange,提问作者Arthur Eirich

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 16:23:12