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

Groovy测试Spring Boot WebClient PUT请求触发空指针异常

问题分析与解决方案

核心问题

触发NullPointerException的原因有两个:

  1. Spock Mock交互规则冲突:你在given块中定义了webClientMock.put() >> bodySpec存根,但又在then块中声明1 * webClientMock.put()进行调用次数验证。Spock的Mock默认是严格模式,这种分开的声明会导致存根被覆盖,实际调用时webClient.put()返回null。
  2. 方法存根不匹配:待测试Java代码中调用的是body(Mono.just(...), CacheApiRequest.class),但测试代码里存根的是bodyValue(_),两者是不同的重载方法,存根无法命中,会导致后续链式调用返回null。

修复步骤

1. 调整Spock Mock的存根与验证方式

将存根和调用次数验证合并声明,或者确保存根在验证之前且逻辑一致。推荐合并声明,避免冲突。

2. 修正WebClient方法存根的匹配

将bodyValue的存根改为匹配实际调用的body(Publisher, Class)重载方法。

3. 补充静态方法Mock(如果需要)

如果AuthUtils.getFuncional和Utils.getFuncionalToogle是静态方法,需要额外Mock(比如用Mockito的静态Mock),否则会执行真实逻辑,可能影响测试流程。

修改后的测试代码示例

@Unroll
def "must call putProcessing"() {
    println "Entering the test putProcessing"
    
    given: "a valid auth"
    Auth auth = Fixture.from(Auth.class).gimme("VALID")

    and: "a valid tokenParser"
    TokenParser jwtParser = Mock(TokenParser)

    and : "a mocked claims "
    Claims claims = Mock(Claims)
    claims.get("codigoFuncional", String.class) >> "1234-56"
    jwtParser.getBody("valid.jwt.token") >> claims

    and: "a mocked CacheApiRequest "
    CacheApiRequest cacheApiRequestMock = new CacheApiRequest("true", 1L)
    cacheMapper.map("true", 1L) >> cacheApiRequestMock

    and: "WebClient mocks are properly stubbed and verified"
    WebClient.RequestBodyUriSpec bodySpec = Mock(WebClient.RequestBodyUriSpec)
    WebClient.RequestHeadersSpec headerSpec = Mock(WebClient.RequestHeadersSpec)
    WebClient.ResponseSpec responseSpec = Mock(WebClient.ResponseSpec)

    // 合并存根与调用次数验证,确保每个链式调用都被正确匹配
    1 * webClientMock.put() >> bodySpec
    1 * bodySpec.uri(_ as String, _ as Function) >> bodySpec
    2 * bodySpec.header(_ as String, _ as String) >> bodySpec
    1 * bodySpec.contentType(MediaType.APPLICATION_JSON) >> bodySpec
    1 * bodySpec.body(_ as Mono, CacheApiRequest.class) >> headerSpec
    1 * headerSpec.retrieve() >> responseSpec
    1 * responseSpec.bodyToMono(Void.class) >> Mono.empty()

    when: "call putProcessing"
    println "Starting putProcessing"
    repository.putProcessing(auth, jwtParser, "true")

    then: "all expected interactions are satisfied"
    // 此处无需重复验证webClient.put(),因为已在given块中合并声明
    println "test completed"
}

额外注意事项

  • 如果AuthUtils.getFuncional是静态方法,需要添加静态Mock:
    given: "mocked static utils"
    Mockito.mockStatic(AuthUtils.class)
    AuthUtils.getFuncional(auth, jwtParser) >> "1234-56"
    
  • 同理,Utils.getFuncionalToogle也需要静态Mock:
    Mockito.mockStatic(Utils.class)
    Utils.getFuncionalToogle(_ as String) >> "1234-56"
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 10:38:10