Spring WebClient Mockito测试POST请求时body()空指针问题
WebClient Mock测试中body()方法返回NullPointerException的解决方法
问题现象
测试时抛出空指针异常:
java.lang.NullPointerException: Cannot invoke "org.springframework.web.reactive.function.client.WebClient$RequestHeadersSpec.retrieve()" because the return value of "org.springframework.web.reactive.function.client.WebClient$RequestBodySpec.body(org.reactivestreams.Publisher, java.lang.Class)" is null
业务代码
import com.bluelagoon.payetonkawa.dolibarr.entities.input.LoginEntity; import com.bluelagoon.payetonkawa.dolibarr.entities.output.SuccessEntity; import com.bluelagoon.payetonkawa.dolibarr.entities.output.UserInfoEntity; import com.bluelagoon.payetonkawa.dolibarr.services.DolibarrInfraService; import org.springframework.stereotype.Service; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; @Service public class DolibarrInfraServiceImpl implements DolibarrInfraService { private final WebClient webClient; public DolibarrInfraServiceImpl(WebClient webClient) { this.webClient = webClient; } @Override public SuccessEntity login(LoginEntity login) { return webClient.post() .uri("/login") .body(Mono.just(login), LoginEntity.class) .retrieve() .bodyToMono(SuccessEntity.class) .block(); } ... }
测试代码
package com.bluelagoon.payetonkawa.dolibarr.impl; import com.bluelagoon.payetonkawa.dolibarr.entities.input.LoginEntity; import com.bluelagoon.payetonkawa.dolibarr.entities.output.SuccessEntity; import com.bluelagoon.payetonkawa.dolibarr.entities.output.TokenEntity; import com.bluelagoon.payetonkawa.dolibarr.services.DolibarrInfraService; 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.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; import java.net.http.HttpRequest; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.lenient; @ExtendWith(MockitoExtension.class) class DolibarrInfraServiceImplTest { @Mock private 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; @InjectMocks private DolibarrInfraServiceImpl dolibarrInfraService; @Test void loginTest_should_return_a_valid_SuccessEntity(){ var loginEntity = new LoginEntity(); loginEntity.setLogin("test"); loginEntity.setPassword("test"); var tokenEntity = new TokenEntity(); tokenEntity.setCode(200); tokenEntity.setToken("test"); var successEntity = new SuccessEntity(); successEntity.setSuccess(tokenEntity); when(webClientMock.post()) .thenReturn(requestBodyUriSpecMock); when(requestBodyUriSpecMock.uri(anyString())) .thenReturn(requestBodySpecMock); when(requestBodySpecMock.body(Mono.just(loginEntity), LoginEntity.class)) .thenReturn(requestHeadersSpecMock); when(requestHeadersSpecMock.retrieve()) .thenReturn(responseSpecMock); when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<SuccessEntity>>notNull())) .thenReturn(Mono.just(successEntity)); var expectedTokenEntity = new TokenEntity(); expectedTokenEntity.setToken("test"); expectedTokenEntity.setCode(200); var expectedSuccessEntity = new SuccessEntity(); expectedSuccessEntity.setSuccess(expectedTokenEntity); var resultSuccessEntity = dolibarrInfraService.login(loginEntity); assertEquals(expectedSuccessEntity, resultSuccessEntity); } }
问题原因
测试代码中使用具体的Mono实例(Mono.just(loginEntity))作为body()方法的匹配参数,但业务代码中创建的Mono.just(login)是全新的实例,两者不是同一个对象,导致Mockito找不到对应的存根配置,返回null进而触发空指针异常。
解决方案
将body()方法的参数匹配改为通用匹配器,替换为any(Mono.class)和any(Class.class),修改后的存根代码如下:
when(requestBodySpecMock.body(any(Mono.class), any(Class.class))) .thenReturn(requestHeadersSpecMock);
修改后的完整测试方法
@Test void loginTest_should_return_a_valid_SuccessEntity(){ var loginEntity = new LoginEntity(); loginEntity.setLogin("test"); loginEntity.setPassword("test"); var tokenEntity = new TokenEntity(); tokenEntity.setCode(200); tokenEntity.setToken("test"); var successEntity = new SuccessEntity(); successEntity.setSuccess(tokenEntity); when(webClientMock.post()) .thenReturn(requestBodyUriSpecMock); when(requestBodyUriSpecMock.uri(anyString())) .thenReturn(requestBodySpecMock); // 替换为通用参数匹配器 when(requestBodySpecMock.body(any(Mono.class), any(Class.class))) .thenReturn(requestHeadersSpecMock); when(requestHeadersSpecMock.retrieve()) .thenReturn(responseSpecMock); when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<SuccessEntity>>notNull())) .thenReturn(Mono.just(successEntity)); var expectedTokenEntity = new TokenEntity(); expectedTokenEntity.setToken("test"); expectedTokenEntity.setCode(200); var expectedSuccessEntity = new SuccessEntity(); expectedSuccessEntity.setSuccess(expectedTokenEntity); var resultSuccessEntity = dolibarrInfraService.login(loginEntity); assertEquals(expectedSuccessEntity, resultSuccessEntity); }
可选:精确匹配(需额外配置)
如果需要对请求体内容做精确匹配,需确保LoginEntity正确实现equals()和hashCode()方法,再使用eq()匹配器:
when(requestBodySpecMock.body(eq(Mono.just(loginEntity)), eq(LoginEntity.class))) .thenReturn(requestHeadersSpecMock);
内容的提问来源于stack exchange,提问作者Tokoro-San
相关产品推荐
相关产品推荐

