如何在@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
相关产品推荐
相关产品推荐

