Mock WebClient调用获取Okta Token时触发NullPointerException问题求助
解决Mock WebClient获取Okta Token时的NullPointerException问题
你的问题主要有两个核心原因:
- 业务代码中在方法内部直接创建WebClient实例,导致你测试中Mock的WebClient根本没被使用到
- 测试中的Mock链不完整,且对WebClient反应式方法的Mock方式错误
步骤1:重构业务代码,让WebClient可被注入/Mock
首先,你需要把WebClient的创建逻辑从getOktaToken()方法中抽离出来,这样测试时才能替换成Mock实例。比如:
修改后的业务代码
// 在类级别注入或创建WebClient,而不是方法内 private final WebClient webClient; private final ObjectMapper objectMapper; // 其他必要的成员变量:proxyHost、proxyPort、baseUrl、username、password、path等 // 构造方法注入,或者用@Autowired(如果是Spring环境) public StormUtility(String proxyHost, int proxyPort, String baseUrl, String username, String password, ObjectMapper objectMapper) { HttpClient httpClient = HttpClient.create() .proxy(proxy -> proxy.type(ProxyProvider.Proxy.HTTP) .host(proxyHost) .port(proxyPort)); ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient); this.webClient = WebClient.builder() .clientConnector(connector) .baseUrl(baseUrl) .filter(ExchangeFilterFunctions.basicAuthentication(username, password)) .defaultUriVariables(Collections.singletonMap("url", baseUrl)) .build(); this.objectMapper = objectMapper; } public String getOktaToken() { String result; if (getNewToken()) { ResponseEntity<String> response = webClient.post() // 现在使用类级别的webClient .uri(uriBuilder -> uriBuilder .path(path) .queryParam("grant_type", "client_credentials") .queryParam("response_type", "token") .queryParam("scope", "SOME_SCOPE") .build()) .contentType(MediaType.APPLICATION_FORM_URLENCODED) .retrieve() .onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error( new RuntimeException("We got a client error: " + clientResponse.statusCode().value()))) .onStatus(HttpStatus::is5xxServerError, clientResponse -> Mono.error( new RuntimeException("We got a server error: " + clientResponse.statusCode().value()))) .toEntity(String.class) .block(); assert response != null; if (response.getStatusCodeValue() == HttpStatus.OK.value()) { try { OktaResponse oktaResponse = objectMapper.readValue(response.getBody(), OktaResponse.class); result = oktaResponse.getAccessToken(); jwt = JWTParser.parse(oktaResponse.getAccessToken()); jwtRaw = oktaResponse.getAccessToken(); } catch (Exception e) { throw new StormException("Unable to parse JSON object", e); } } else { throw new RuntimeException("Something Unexpected Happened. Http Response Code: " + response.getStatusCodeValue()); } } else { result = jwtRaw; } return result; }
如果是Spring环境,更推荐通过@Bean定义WebClient,然后用@Autowired注入到你的StormUtility类中,这样测试时可以用@MockBean轻松替换。
步骤2:修正测试用例的Mock链
现在你可以Mock类中的WebClient实例了,同时需要修正Mock链中的错误,特别是反应式方法的Mock(toEntity返回的是Mono<ResponseEntity<String>>,需要Mock返回一个已完成的Mono):
修改后的测试代码
@ExtendWith(MockitoExtension.class) class StormUtilityTest { @Mock private WebClient webClient; @Mock private WebClient.RequestBodyUriSpec requestBodyUriSpec; @Mock private WebClient.RequestBodySpec requestBodySpec; @Mock private WebClient.ResponseSpec responseSpec; @InjectMocks private StormUtility stormUtility; // 自动将Mock的webClient注入到测试类中 @Test void getOktaToken() throws Exception { // 准备模拟的响应体,要符合OktaResponse的JSON格式 String mockResponseBody = "{\"access_token\":\"mock-okta-token-123\"}"; ResponseEntity<String> mockResponse = new ResponseEntity<>(mockResponseBody, HttpStatus.OK); // 构建完整的Mock调用链 when(webClient.post()).thenReturn(requestBodyUriSpec); // 匹配业务代码中使用的uri(UriBuilder)重载方法 when(requestBodyUriSpec.uri(any(Function.class))).thenReturn(requestBodySpec); when(requestBodySpec.contentType(MediaType.APPLICATION_FORM_URLENCODED)).thenReturn(requestBodySpec); when(requestBodySpec.retrieve()).thenReturn(responseSpec); // Mock两个onStatus链式调用,返回自身以维持调用链 when(responseSpec.onStatus(any(Predicate.class), any(Function.class))).thenReturn(responseSpec); when(responseSpec.onStatus(any(Predicate.class), any(Function.class))).thenReturn(responseSpec); // Mock toEntity方法,返回包含模拟响应的Mono(WebClient是反应式框架,必须返回Mono) when(responseSpec.toEntity(String.class)).thenReturn(Mono.just(mockResponse)); // 调用测试方法 String result = stormUtility.getOktaToken(); // 验证结果是否符合预期 assertEquals("mock-okta-token-123", result); } }
关键修正点说明
- WebClient注入:通过
@InjectMocks把Mock的WebClient注入到被测试类,确保业务代码使用的是Mock实例,而不是方法内新创建的实例 - Uri方法匹配:业务代码中使用的是
uri(UriBuilder)重载,所以测试中要用when(requestBodyUriSpec.uri(any(Function.class)))来匹配,而不是anyString() - 反应式方法Mock:
toEntity返回Mono类型,必须用Mono.just(mockResponse)来模拟返回值,不能直接返回ResponseEntity - 链式调用Mock:确保每个链式调用的方法都Mock返回对应的Spec实例(比如
contentType返回requestBodySpec,onStatus返回responseSpec),避免链式调用中出现Null
这样修改后,你的测试应该就能正常运行,不会再出现NullPointerException了。
内容的提问来源于stack exchange,提问作者Howell Evans
相关产品推荐
相关产品推荐

