如何对带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¶m2=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¶m2=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
相关产品推荐
相关产品推荐

