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

如何在@Service类中Mock Spring @Autowired WebClient的响应?

Spring WebClient 接口Mock测试实现方案

方案选型说明

  • 优先选择MockWebServer方案:不需要修改业务代码,不需要Mock WebClient内部的流式调用链,真实模拟HTTP请求/响应编解码全流程,测试稳定性高,不会因为业务代码调整WebClient调用顺序、增加Header参数就失效。
  • 不推荐直接逐层Mock WebClient方法链:这种写法耦合度极高,维护成本大,仅适合完全不需要走HTTP编解码逻辑的简单场景。

方案一:MockWebServer 实现(推荐)

1. 引入测试依赖

Maven 依赖配置:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>mockwebserver</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.projectreactor</groupId>
    <artifactId>reactor-test</artifactId>
    <scope>test</scope>
</dependency>

Gradle 依赖配置:

testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation 'io.projectreactor:reactor-test'

2. 测试类基础配置

移除原来测试类里硬编码的生产地址配置,每个测试用例启动前自动绑定空闲端口启动本地Mock服务,把业务配置的API地址指向Mock服务地址,业务代码完全无感知:

@SpringBootTest
class ApplicationTests {
    // 自动寻找空闲端口启动本地Mock服务器,避免端口冲突
    private final MockWebServer mockWebServer = new MockWebServer();

    @Autowired
    private ModelService modelService;

    @Autowired
    private ApiPropertiesConfig apiProperties;

    @BeforeEach
    void setUp() throws IOException {
        mockWebServer.start();
        // 覆盖业务配置的API地址,指向本地Mock服务
        apiProperties.setApiUrl(mockWebServer.url("/").toString());
    }

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

    // 通用工具方法:从test/resources目录读取本地JSON文件
    private String readJsonFromFile(String fileName) throws IOException {
        Path jsonPath = Paths.get("src/test/resources/json", fileName);
        return Files.readString(jsonPath);
    }
}

提前把测试用的JSON文件放到src/test/resources/json目录下,比如非法响应的invalid_model.json、不同业务场景的合法响应valid_model_scene1.json、valid_model_scene2.json。

3. 两类测试场景实现

场景1:验证DTO校验逻辑生效

针对不符合@NotNull、@Size校验规则的非法JSON/格式错误JSON,校验异常抛出逻辑:

@Test
void testInvalidResponse_shouldThrowValidException() throws Exception {
    // 给Mock服务塞入非法JSON响应
    String invalidJson = readJsonFromFile("invalid_model.json");
    mockWebServer.enqueue(new MockResponse()
            .setResponseCode(200)
            .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .setBody(invalidJson)
    );

    // 用reactor-test的StepVerifier校验响应结果
    StepVerifier.create(modelService.getModel())
            .expectErrorMatches(throwable -> 
                throwable instanceof ConstraintViolationException // DTO校验失败异常
                || throwable instanceof DecodingException // JSON格式解析异常
            )
            .verify();

    // 可选:校验请求格式符合预期
    RecordedRequest receivedRequest = mockWebServer.takeRequest();
    assertEquals("GET", receivedRequest.getMethod());
    assertEquals(MediaType.APPLICATION_JSON_VALUE, receivedRequest.getHeader(HttpHeaders.ACCEPT));
}

提示:如果测试中DTO校验没有自动触发,可以在WebClient配置中注册JSR-380校验编解码器,或者在bodyToMono(ModelDTO.class)后增加校验逻辑手动触发。

场景2:验证合法响应的业务逻辑

针对不同合法JSON映射出的Model对象,校验下游依赖ModelService的业务行为:

@Test
void testValidResponse_scene1_shouldMatchBusinessRule() throws Exception {
    String validJson = readJsonFromFile("valid_model_scene1.json");
    mockWebServer.enqueue(new MockResponse()
            .setResponseCode(200)
            .addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
            .setBody(validJson)
    );

    StepVerifier.create(modelService.getModel())
            .assertNext(model -> {
                // 断言Model字段映射正确
                assertEquals("expectedValue", model.getBizField());
                // 如果要测试依赖ModelService的其他Bean,直接@Autowired注入后调用方法断言结果即可
            })
            .verifyComplete();
}

方案二:直接Mock WebClient实例(不推荐)

如果不需要走HTTP编解码流程,想直接替换Spring容器中注入的WebClient为Mock实例,用@MockBean注解即可,Spring Boot测试会自动替换容器中原有同类型Bean:

@SpringBootTest
@ExtendWith(MockitoExtension.class)
class ApplicationTests {
    // 自动替换容器中注入的WebClient实例为Mock对象
    @MockBean
    private WebClient webClient;

    // Mock WebClient流式调用链涉及的中间对象
    @Mock
    private WebClient.RequestHeadersUriSpec requestHeadersUriSpec;
    @Mock
    private WebClient.RequestHeadersSpec requestHeadersSpec;
    @Mock
    private WebClient.ResponseSpec responseSpec;

    @BeforeEach
    void setUp() {
        // 逐层Mock调用逻辑,返回预设的DTO对象/异常
        when(webClient.get()).thenReturn(requestHeadersUriSpec);
        when(requestHeadersUriSpec.uri(anyString())).thenReturn(requestHeadersSpec);
        when(requestHeadersSpec.accept(any(MediaType.class))).thenReturn(requestHeadersSpec);
        when(requestHeadersSpec.retrieve()).thenReturn(responseSpec);
        // 这里可以直接返回从本地JSON反序列化得到的ModelDTO对象,或者返回错误Mono
        when(responseSpec.bodyToMono(eq(ModelDTO.class))).thenReturn(Mono.just(testValidDto));
    }
}

注意:这种写法非常脆弱,只要业务代码调整了WebClient的调用顺序、增加了Header参数、修改了接口调用方式,Mock逻辑就会直接失效,需要同步修改测试代码,维护成本很高。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:01:14