Mock POST类型WebClient时触发NullPointerException异常排查
WebClient单元测试NullPointerException异常排查
java.lang.NullPointerException: 无法调用"org.springframework.web.reactive.function.client.WebClient$RequestBodySpec.body(org.reactivestreams.Publisher, java.lang.Class)",因为"org.springframework.web.reactive.function.client.WebClient$RequestBodySpec.headers(java.util.function.Consumer)"的返回值为null
相关Controller代码
package com.bezkoder.springjwt.controllers; import com.bezkoder.springjwt.models.Product; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; import reactor.core.publisher.Mono; import java.util.Arrays; import java.util.function.Consumer; @RestController public class ProductClientController { @Autowired WebClient webClient; @GetMapping("/catproducts") public String callSaveProductApi() { String response = null; Product product = new Product(); product.setId(1l); product.setName("kumar2"); product.setPrice(88); try { response = webClient.post() .uri(UriComponentsBuilder.fromUriString("http://localhost:8080/products").build().toString()) .headers(getHttpHeadersConsumer()).body(Mono.just(product), Product.class) .retrieve().bodyToMono(String.class).block(); } catch (Exception e) { System.out.println(e); } return response; } private Consumer<HttpHeaders> getHttpHeadersConsumer() { return httpHeaders -> { httpHeaders.setContentType(MediaType.APPLICATION_JSON); httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); }; } }
相关测试类代码
package com.bezkoder.springjwt.controllers; import com.bezkoder.springjwt.models.Product; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentMatchers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class ProductClientControllerTest { @InjectMocks ProductClientController productClientController; @Mock WebClient webClientMock; @Mock private WebClient.RequestBodyUriSpec requestBodyUriSpecMock; @Mock private WebClient.RequestBodySpec requestBodySpecMock; @Mock private WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock; @Mock private WebClient.RequestHeadersSpec requestHeadersSpecMock; @Mock private WebClient.ResponseSpec responseSpecMock; @Test void callSaveProductApi(){ Product product = new Product(); product.setId(1l); product.setName("kumar2"); product.setPrice(88); when(webClientMock.post()) .thenReturn(requestBodyUriSpecMock); when(requestBodyUriSpecMock.uri(anyString())) .thenReturn(requestBodySpecMock); when(requestBodySpecMock.body(Mono.just(product), Product.class)) .thenReturn(requestHeadersSpecMock); when(requestBodyUriSpecMock.headers(any())).thenReturn(requestBodySpecMock); when(requestHeadersSpecMock.retrieve()) .thenReturn(responseSpecMock); when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<String>>notNull())) .thenReturn(Mono.just("success")); String s = productClientController.callSaveProductApi(); } }
异常原因
- Mock调用链不匹配:Controller中调用顺序是
webClient.post() -> uri() -> headers() -> body(),其中headers()是调用uri()返回的RequestBodySpec对象的方法,但测试中仅Mock了requestBodyUriSpecMock.headers(),未MockrequestBodySpecMock.headers(),导致实际调用时headers()返回null,后续调用body()触发空指针。 - 参数匹配过于严格:测试中
body()方法用Mono.just(product)作为参数匹配,但Controller内部创建了新的Product实例,两者不是同一个对象,导致Mock的body()方法不生效,也会引发调用链中断。
解决方法
步骤1:修正RequestBodySpec.headers()的Mock
添加对requestBodySpecMock.headers()的Mock,让它返回自身以维持链式调用:
when(requestBodySpecMock.headers(any(Consumer.class))).thenReturn(requestBodySpecMock);
步骤2:放宽body()方法的参数匹配
使用any(Mono.class)和eq(Product.class)来匹配任意Mono实例和指定的类,避免因对象实例不同导致Mock失效:
// 需要导入 org.mockito.ArgumentMatchers.eq when(requestBodySpecMock.body(any(Mono.class), eq(Product.class))).thenReturn(requestHeadersSpecMock);
完整修正后的测试方法
@Test void callSaveProductApi(){ Product product = new Product(); product.setId(1l); product.setName("kumar2"); product.setPrice(88); when(webClientMock.post()).thenReturn(requestBodyUriSpecMock); when(requestBodyUriSpecMock.uri(anyString())).thenReturn(requestBodySpecMock); // 关键:Mock RequestBodySpec的headers方法返回自身 when(requestBodySpecMock.headers(any(Consumer.class))).thenReturn(requestBodySpecMock); // 放宽参数匹配 when(requestBodySpecMock.body(any(Mono.class), eq(Product.class))).thenReturn(requestHeadersSpecMock); when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock); when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<String>>notNull())).thenReturn(Mono.just("success")); String s = productClientController.callSaveProductApi(); // 可选:添加断言验证结果 // assertEquals("success", s); }
内容的提问来源于stack exchange,提问作者Carlene12War
相关产品推荐
相关产品推荐

