Spring Boot集成测试中Mock WebClient的正确实现方案求助
解决方案:Mock WebClient的外部API响应
要在保留UserService原有业务逻辑(包括数据库操作)的前提下,仅Mock WebClient的外部API返回结果,核心思路是替换WebClient底层的请求执行组件,而非Mock整个服务类。以下是两种可行方案:
方法一:使用MockWebServer(推荐,贴近真实请求场景)
MockWebServer是OkHttp提供的本地Mock服务器,可让WebClient请求指向它,返回预设响应。
1. 添加测试依赖
在pom.xml(Maven)中引入依赖:
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>mockwebserver</artifactId> <scope>test</scope> </dependency>
2. 编写集成测试类
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest @AutoConfigureMockMvc public class UserControllerIntegrationTest { private MockWebServer mockWebServer; @Autowired private MockMvc mockMvc; @BeforeEach void setUp() throws Exception { mockWebServer = new MockWebServer(); mockWebServer.start(); // 替换服务中的外部API地址为Mock服务器地址 System.setProperty("external.api.uri", mockWebServer.url("/").toString()); } @AfterEach void tearDown() throws Exception { mockWebServer.shutdown(); } @Test void getAllUsers_ReturnsMockedList() throws Exception { // 预设外部API的Mock响应 String mockUserList = "[{\"id\":\"1\",\"name\":\"Alice\"},{\"id\":\"2\",\"name\":\"Bob\"}]"; mockWebServer.enqueue(new MockResponse() .setResponseCode(200) .setHeader("Content-Type", "application/json") .setBody(mockUserList)); // 调用控制器接口并验证结果 mockMvc.perform(get("/api/users")) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].id").value("1")) .andExpect(jsonPath("$[1].name").value("Bob")); } @Test void getUserById_ReturnsMockedUser() throws Exception { String userId = "1"; String mockUser = "{\"id\":\"1\",\"name\":\"Alice\"}"; mockWebServer.enqueue(new MockResponse() .setResponseCode(200) .setHeader("Content-Type", "application/json") .setBody(mockUser)); mockMvc.perform(get("/api/users/{id}", userId)) .andExpect(status().isOk()) .andExpect(jsonPath("$.id").value(userId)) .andExpect(jsonPath("$.name").value("Alice")); } @Test void getUserById_Returns404WhenNotFound() throws Exception { String userId = "999"; mockWebServer.enqueue(new MockResponse().setResponseCode(404)); mockMvc.perform(get("/api/users/{id}", userId)) .andExpect(status().isNotFound()); } }
注意事项
- 确保
UserService中的uri通过配置属性注入(比如@Value("${external.api.uri}")),方便测试时动态替换为Mock服务器地址。 - 每个测试用例独立启动/关闭Mock服务器,避免端口占用冲突。
方法二:直接Mock ExchangeFunction(轻量级,无需启动服务器)
通过Mock WebClient的核心执行组件ExchangeFunction,直接返回预设响应,无需依赖外部服务器。
编写测试类
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ExchangeFunction; import reactor.core.publisher.Mono; import static org.springframework.http.HttpHeaders.CONTENT_TYPE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @SpringBootTest @AutoConfigureMockMvc public class UserControllerIntegrationTest { @Autowired private MockMvc mockMvc; @Autowired private Repository repository; // 配置测试用WebClient,使用Mock的ExchangeFunction @TestConfiguration static class TestConfig { @Bean public WebClient webClient(ExchangeFunction mockExchangeFunction) { return WebClient.builder() .exchangeFunction(mockExchangeFunction) .build(); } } @Test void getAllUsers_ReturnsMockedList() throws Exception { // 构建Mock响应体 String mockBody = "[{\"id\":\"1\",\"name\":\"Alice\"}]"; ClientResponse mockResponse = ClientResponse.create(HttpStatus.OK) .header(CONTENT_TYPE, APPLICATION_JSON_VALUE) .body(mockBody) .build(); // Mock ExchangeFunction,直接返回预设响应 ExchangeFunction mockExchange = request -> Mono.just(mockResponse); // 注入Mock WebClient到服务(或通过TestConfig全局替换) UserService userService = new UserService( WebClient.builder().exchangeFunction(mockExchange).build(), "/users", repository ); mockMvc.perform(get("/api/users")) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].name").value("Alice")); } @Test void getUserById_ThrowsNotFound() throws Exception { // 构建404的Mock响应 ClientResponse notFoundResponse = ClientResponse.create(HttpStatus.NOT_FOUND).build(); ExchangeFunction mockExchange = request -> Mono.just(notFoundResponse); UserService userService = new UserService( WebClient.builder().exchangeFunction(mockExchange).build(), "/users/", repository ); mockMvc.perform(get("/api/users/999")) .andExpect(status().isNotFound()); } }
核心要点
- 两种方案均不Mock
UserService,保留其内部数据库操作等业务逻辑,仅拦截WebClient的外部请求。 - 无论服务使用
retrieve()还是exchangeToMono(),都可通过上述方式Mock响应,因为最终都依赖ExchangeFunction执行请求。 - 使用
@SpringBootTest时,确保测试上下文正确加载Mock的WebClient Bean,覆盖默认Bean。
内容的提问来源于stack exchange,提问作者Riggster
相关产品推荐
相关产品推荐

