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

在Spock框架中编写Spring WebFlux集成测试时Mock WebClient并Stub数据

在Spock中Mock Spring WebFlux的WebClient并Stub外部API响应

1. 测试类基础配置

  • 使用@SpringBootTest启动Spring上下文,通过@MockBean替换应用中真实的WebClient或WebClient.Builder(取决于你应用的注入方式),避免发送真实的外部API请求。
  • 如果应用通过WebClient.Builder构建实例,就MockWebClient.Builder;若直接注入WebClient实例,直接MockWebClient即可。

2. Stub WebClient的链式调用

WebClient的调用是链式结构,需要逐步Mock每一步的返回值,最终Stub出目标响应。以下是具体示例:

示例场景:应用通过WebClient GET请求外部API

假设你的业务服务类如下:

@Service
public class ExternalApiService {
    private final WebClient webClient;

    public ExternalApiService(WebClient webClient) {
        this.webClient = webClient;
    }

    public Mono<ExternalResponse> fetchData(String id) {
        return webClient.get()
                .uri("/external/api/{id}", id)
                .retrieve()
                .bodyToMono(ExternalResponse.class);
    }
}

对应的Spock集成测试代码:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ExternalApiIntegrationSpec extends Specification {

    @MockBean
    WebClient webClient

    @Autowired
    ExternalApiService externalApiService

    // 注入RestAssured的RequestSpecification用于发起应用API请求
    @Autowired
    RequestSpecification requestSpecification

    def "测试应用API调用时,WebClient返回Stub数据"() {
        given: "Stub WebClient的全链路调用"
        def mockUriSpec = Mock(WebClient.RequestHeadersUriSpec)
        def mockRequestHeadersSpec = Mock(WebClient.RequestHeadersSpec)
        def mockResponseSpec = Mock(WebClient.ResponseSpec)
        def stubbedExternalResp = new ExternalResponse(id: "123", content: "mock external data")

        // 逐步Stub每一步的返回结果
        webClient.get() >> mockUriSpec
        mockUriSpec.uri("/external/api/{id}", "123") >> mockRequestHeadersSpec
        mockRequestHeadersSpec.retrieve() >> mockResponseSpec
        mockResponseSpec.bodyToMono(ExternalResponse.class) >> Mono.just(stubbedExternalResp)

        when: "通过RequestSpecification调用应用API"
        def appResp = requestSpecification
                .get("/app/internal/data/123")
                .then()
                .statusCode(200)
                .extract()
                .as(AppResponse.class)

        then: "验证返回数据来自Stub的WebClient响应"
        appResp.content == "mock external data"
    }
}

3. 适配POST等其他HTTP方法

如果是POST请求,只需调整起始方法的Stub逻辑,后续步骤同理:

// Stub POST请求起始
webClient.post() >> mockUriSpec
// Stub URI和请求体
mockUriSpec.uri("/external/api/create") >> mockRequestHeadersSpec
mockRequestHeadersSpec.body(BodyInserters.fromValue(_ as CreateRequest)) >> mockRequestHeadersSpec

4. Stub错误场景

比如模拟外部API返回404错误:

mockResponseSpec.onStatus({ it.is4xxClientError() }, _) >> Mono.error(new WebClientResponseException(
        404,
        "Not Found",
        null,
        null,
        null,
        null
))

5. 额外注意事项

  • 确保@MockBean正确替换了应用中的WebClient实例,避免真实请求泄漏。
  • Spock支持灵活的参数匹配,用_匹配任意参数,eq()做精确匹配,可根据测试场景调整。
  • 若应用通过WebClient.Builder构建实例,需额外Stubbuild()方法:
    @MockBean
    WebClient.Builder webClientBuilder
    
    def setup() {
        webClientBuilder.build() >> webClient
    }
    

内容的提问来源于stack exchange,提问作者Srikanth Reddy Kallem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 15:57:46