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

Mockito深度存根遇Lambda参数时卡顿的原因及通用解决方案

测试WebClient Mock链含Lambda时冻结的问题分析与解决

现象描述

使用Mockito模拟WebClient调用链时,只要链中包含Lambda(或方法引用),测试就会冻结,无法在合理时间内完成。以下是最小复现示例:

冻结的测试用例

package com.example.dynamicgateway.service.applicationDocClient;

import io.swagger.v3.parser.OpenAPIV3Parser;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.junit.jupiter.api.Test;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class WebClientTest {
    @Test
    void test() {
        OpenAPIV3Parser parserMock = mock(OpenAPIV3Parser.class);
        SwaggerParseResult parseResultMock = mock(SwaggerParseResult.class);
        WebClient webClientMock = mock(WebClient.class, RETURNS_DEEP_STUBS);
        when(webClientMock
                .get()
                .uri("https://example.com/docs")
                .retrieve()
                .bodyToMono(String.class)
                .map(parserMock::readContents)
        ).thenReturn(Mono.just(parseResultMock));

        Mono<SwaggerParseResult> stringMono = webClientMock
                .get()
                .uri("https://example.com/docs")
                .retrieve()
                .bodyToMono(String.class)
                .map(parserMock::readContents);

        StepVerifier.create(stringMono)
                .expectNext(parseResultMock)
                .expectComplete()
                .verify();
    }
}

移除Lambda后的正常测试用例

去掉调用链中的mapLambda后,测试可正常通过:

@Test
void test() {
    String responseString = "response-string";
    WebClient webClientMock = mock(WebClient.class, RETURNS_DEEP_STUBS);
    when(webClientMock
            .get()
            .uri("https://example.com/docs")
            .retrieve()
            .bodyToMono(String.class)
    ).thenReturn(Mono.just(responseString));

    Mono<String> stringMono = webClientMock
            .get()
            .uri("https://example.com/docs")
            .retrieve()
            .bodyToMono(String.class);

    StepVerifier.create(stringMono)
            .expectNext(responseString)
            .expectComplete()
            .verify();
}

Lambda不涉及Mock对象的冻结用例

即使Lambda不依赖Mock对象,测试依然会冻结:

@Test
void test() {
    WebClient webClientMock = mock(WebClient.class, RETURNS_DEEP_STUBS);
    String uri = "https://example.com";
    Integer expectedInt = 42;
    when(webClientMock
            .get()
            .uri(uri)
            .retrieve()
            .bodyToMono(String.class)
            .map(Integer::valueOf)
    ).thenReturn(Mono.just(expectedInt));

    Mono<Integer> integerMono = webClientMock
            .get()
            .uri(uri)
            .retrieve()
            .bodyToMono(String.class)
            .map(Integer::valueOf);

    StepVerifier.create(integerMono)
            .expectNext(expectedInt)
            .expectComplete()
            .verify();
}

问题

  1. 该现象的原因是什么?
  2. 如何解决?尤其是调用链更长、Lambda参数遍布的复杂场景?

原因分析

  1. Lambda匹配失败:Mockito默认的参数匹配逻辑无法识别when语句中的Lambda和测试代码中实际调用的Lambda是同一个对象——Lambda的equals/hashCode实现依赖JVM,没有统一的判定标准。
  2. 空Mono阻塞:当Mockito找不到匹配的存根时,会返回一个空的Mono,而StepVerifier会一直等待这个Mono发出信号,最终导致测试冻结。

解决方案

1. 拆分Mock链(简单场景)

将包含Lambda的调用链拆分为多个独立的Mock存根,先Mock出bodyToMono的结果,再让后续的map逻辑基于该结果执行:

@Test
void test() {
    SwaggerParseResult parseResultMock = mock(SwaggerParseResult.class);
    String serializedParseResult = serialize(parseResultMock);

    OpenAPIV3Parser parserMock = mock(OpenAPIV3Parser.class);
    when(parserMock.readContents(serializedParseResult)).thenReturn(parseResultMock);

    WebClient webClientMock = mock(WebClient.class, RETURNS_DEEP_STUBS);
    when(webClientMock
            .get()
            .uri("https://example.com/docs")
            .retrieve()
            .bodyToMono(String.class)
    ).thenReturn(Mono.just(serializedParseResult));

    Mono<SwaggerParseResult> parseResultMono = webClientMock
            .get()
            .uri("https://example.com/docs")
            .retrieve()
            .bodyToMono(String.class)
            .map(parserMock::readContents);

    StepVerifier.create(parseResultMono)
            .expectNext(parseResultMock)
            .expectComplete()
            .verify();
}

@SneakyThrows
private String serialize(SwaggerParseResult parseResult) {
    return new ObjectMapper().writeValueAsString(parseResult);
}

2. 使用参数匹配器忽略Lambda(复杂场景)

对于长调用链中遍布Lambda的情况,用Mockito的any()参数匹配器跳过Lambda的匹配,直接为map方法返回预期结果:

import org.mockito.ArgumentMatchers;
import java.util.function.Function;

@Test
void test() {
    WebClient webClientMock = mock(WebClient.class, RETURNS_DEEP_STUBS);
    Integer expectedInt = 42;

    // 用any()匹配任意Function类型的Lambda参数
    when(webClientMock
            .get()
            .uri("https://example.com")
            .retrieve()
            .bodyToMono(String.class)
            .map(ArgumentMatchers.any(Function.class))
    ).thenReturn(Mono.just(expectedInt));

    Mono<Integer> integerMono = webClientMock
            .get()
            .uri("https://example.com")
            .retrieve()
            .bodyToMono(String.class)
            .map(Integer::valueOf);

    StepVerifier.create(integerMono)
            .expectNext(expectedInt)
            .expectComplete()
            .verify();
}

3. 避免使用RETURNS_DEEP_STUBS(推荐)

RETURNS_DEEP_STUBS虽然简化了深链Mock,但会降低测试可读性和可控性,建议手动Mock调用链中的每个中间对象:

@Test
void test() {
    OpenAPIV3Parser parserMock = mock(OpenAPIV3Parser.class);
    SwaggerParseResult parseResultMock = mock(SwaggerParseResult.class);
    String mockResponse = "mock-response";

    // 手动Mock每个中间步骤
    WebClient webClientMock = mock(WebClient.class);
    WebClient.RequestHeadersUriSpec<?> requestSpec = mock(WebClient.RequestHeadersUriSpec.class);
    WebClient.ResponseSpec responseSpec = mock(WebClient.ResponseSpec.class);

    when(webClientMock.get()).thenReturn(requestSpec);
    when(requestSpec.uri("https://example.com/docs")).thenReturn(requestSpec);
    when(requestSpec.retrieve()).thenReturn(responseSpec);
    when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just(mockResponse));
    when(parserMock.readContents(mockResponse)).thenReturn(parseResultMock);

    Mono<SwaggerParseResult> resultMono = webClientMock.get()
            .uri("https://example.com/docs")
            .retrieve()
            .bodyToMono(String.class)
            .map(parserMock::readContents);

    StepVerifier.create(resultMono)
            .expectNext(parseResultMock)
            .expectComplete()
            .verify();
}

这种方式能精准控制每个调用步骤的行为,避免深链Mock带来的隐含问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 07:09:52