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

自定义WebClient的Spock测试中GET请求抛出意外空指针异常

问题原因分析

你的测试抛出NPE是因为**responseSpec.onStatus()的Mock配置错误**:

  • 原代码中onStatus()方法接收两个参数(状态判断Predicate和异常处理Function),且返回的是ResponseSpec对象,用于后续链式调用toBodilessEntity()。
  • 你当前的Mock1 * responseSpec.onStatus() >> Mono.error(...)既没有匹配方法参数,还错误地返回了Mono类型,导致后续调用toBodilessEntity()时,对象实际是Mono而非ResponseSpec,触发空指针异常。
修正后的测试用例
class TransitApiClientSpec extends Specification {

    private WebClient transitApiWebClient
    private TransitApiClient transitApiClient
    private OAuth2CustomClient oAuth2CustomClient
    private TransitApiClientProperties transitApiClientProperties
    private RequestHeadersSpec requestHeadersSpec
    private ResponseSpec responseSpec
    private RequestHeadersUriSpec requestHeadersUriSpec

    def setup() {
        transitApiClientProperties = new TransitApiClientProperties()
        transitApiClientProperties.setServiceUrl("https://test-url")
        transitApiClientProperties.setFindOfficeOfTransit("/transit?")
        transitApiClientProperties.setUsername("username")
        transitApiClientProperties.setPassword("password")
        transitApiClientProperties.setAuthorizationGrantType("grantType")
        transitApiClientProperties.setClientId("clientId")
        transitApiClientProperties.setClientSecret("clientSecret")

        oAuth2CustomClient = Stub(OAuth2CustomClient)
        oAuth2CustomClient.getJwtToken() >> "token"
        transitApiWebClient = Mock(WebClient)
        requestHeadersSpec = Mock(RequestHeadersSpec)
        responseSpec = Mock(ResponseSpec)
        requestHeadersUriSpec = Mock(RequestHeadersUriSpec)
        transitApiClient = new TransitApiClient(transitApiClientProperties, transitApiWebClient, oAuth2CustomClient)
    }

    def "request validation of OoTra and throw InvalidOfficeException"(){
        given :
        def officeId = "testId"
        def uri = UriComponentsBuilder
                .fromUriString(transitApiClientProperties.getFindOfficeOfTransit())
                .queryParam("codelistKey", "CL173")
                .queryParam("itemCode", officeId)
                .build()
                .toUriString()
        def expectedException = new InvalidOfficeException(null, "Invalid Office exception occurred while invoking :/transit?testId")

        1 * transitApiWebClient.get() >> requestHeadersUriSpec
        1 * requestHeadersUriSpec.uri(uri) >> requestHeadersSpec
        1 * requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer token") >> requestHeadersSpec
        1 * requestHeadersSpec.retrieve() >> responseSpec
        // 正确匹配onStatus的参数,并返回ResponseSpec以维持链式调用
        1 * responseSpec.onStatus(
            { it.value() == HttpStatus.NO_CONTENT.value() },
            _ as Function<ClientResponse, Mono<? extends Throwable>>
        ) >> responseSpec
        // 模拟toBodilessEntity抛出预期异常
        1 * responseSpec.toBodilessEntity() >> Mono.error(expectedException)

        when :
        transitApiClient.isOfficeOfTransitValidAndNational(officeId)

        then :
        thrown(InvalidOfficeException)
    }
}
关键修正点
  1. 匹配onStatus的参数:使用Spock的参数匹配器,第一个参数匹配204状态判断逻辑,第二个参数匹配任意异常处理Function。
  2. onStatus返回正确类型:让onStatus返回responseSpec本身,保证链式调用的完整性。
  3. 模拟toBodilessEntity抛出异常:因为原代码中onStatus的逻辑是当状态为204时,在toBodilessEntity()执行时触发异常,所以直接MocktoBodilessEntity()返回Mono.error(),这样调用block()时就会抛出预期的InvalidOfficeException。

内容的提问来源于stack exchange,提问作者JavaEnthusiast27

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 03:15:43