You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mock WebClient调用获取Okta Token时触发NullPointerException问题求助

解决Mock WebClient获取Okta Token时的NullPointerException问题

你的问题主要有两个核心原因:

  1. 业务代码中在方法内部直接创建WebClient实例,导致你测试中Mock的WebClient根本没被使用到
  2. 测试中的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 22:02:47