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(); }
问题
- 该现象的原因是什么?
- 如何解决?尤其是调用链更长、Lambda参数遍布的复杂场景?
原因分析
- Lambda匹配失败:Mockito默认的参数匹配逻辑无法识别
when语句中的Lambda和测试代码中实际调用的Lambda是同一个对象——Lambda的equals/hashCode实现依赖JVM,没有统一的判定标准。 - 空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
相关产品推荐
相关产品推荐

