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

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());
    }
}

核心要点

  • 两种方案均不MockUserService,保留其内部数据库操作等业务逻辑,仅拦截WebClient的外部请求。
  • 无论服务使用retrieve()还是exchangeToMono(),都可通过上述方式Mock响应,因为最终都依赖ExchangeFunction执行请求。
  • 使用@SpringBootTest时,确保测试上下文正确加载Mock的WebClient Bean,覆盖默认Bean。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:22:01