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

如何为返回RequestBodyUriSpec的RestClient编写Spock测试并解决空指针异常

如何正确Mock Spring RestClient的流畅API进行Spock测试?

你当前的测试出现空指针异常,核心原因是没有正确模拟RestClient流畅API的链式调用逻辑。原测试用通配的交互匹配所有方法,导致链式调用中某个环节返回null,触发空指针。

要解决这个问题,需要按RestClient的调用链顺序,逐个Mock每个方法的返回对象,并最终返回包装好的ResponseEntity(因为你的方法返回类型是ResponseEntity<ProductCatalog>,而非直接返回ProductCatalog)。

正确的Spock测试代码

package com.scf.client

import com.scf.domain.model.ProductCatalog
import com.scf.domain.model.ProductSearchRequest
import org.springframework.http.ResponseEntity
import org.springframework.http.MediaType
import org.springframework.web.client.RestClient
import spock.lang.Specification

class ProductRestClientSpec extends Specification {

    // 定义链式调用所需的所有Mock组件
    RestClient restClient = Mock()
    RestClient.RequestBodyUriSpec mockRequestBodyUriSpec = Mock()
    RestClient.RequestBodySpec mockRequestBodySpec = Mock()
    RestClient.ResponseSpec mockResponseSpec = Mock()

    ProductRestClient productRestClient

    def setup() {
        productRestClient = new ProductRestClient(restClient)
    }

    def "Should retrieve a Product Catalog for a valid request"() {
        given:
        def request = createProductSearchRequest()
        def productCatalog = createProductCatalogResponse()
        // 按方法返回类型包装结果
        def expectedResponse = ResponseEntity.ok(productCatalog)

        when:
        def result = productRestClient.findProduct(request)

        then:
        // 严格按调用链顺序Mock每个步骤的返回值
        1 * restClient.post() >> mockRequestBodyUriSpec
        1 * mockRequestBodyUriSpec.uri("/products/catalog") >> mockRequestBodySpec
        1 * mockRequestBodySpec.contentType(MediaType.APPLICATION_JSON) >> mockRequestBodySpec
        1 * mockRequestBodySpec.body(request) >> mockRequestBodySpec
        1 * mockRequestBodySpec.retrieve() >> mockResponseSpec
        1 * mockResponseSpec.toEntity(ProductCatalog.class) >> expectedResponse

        // 验证返回结果的正确性
        result.statusCode.is2xxSuccessful()
        result.body == productCatalog
    }

    def createProductSearchRequest() {
        ProductSearchRequest.builder()
                .catalogId("XF-3333")
                .inStock(true)
                .build()
    }

    def createProductCatalogResponse() {
        ProductCatalog.builder()
                .productDescription("Ralph Lauren Fragrance")
                .productId("PRD-99W222")
                .productName("Ralph Cologne")
                .build()
    }
}

关键说明

  • 链式调用Mock:RestClient的流畅API每个方法都会返回不同的Spec对象,必须按调用顺序逐个指定返回的Mock实例,不能用通配匹配所有方法。
  • 返回类型匹配:目标方法返回的是ResponseEntity<ProductCatalog>,因此需要用ResponseEntity.ok()包装ProductCatalog实例,避免类型不匹配。
  • 交互验证:通过1 *确保每个方法都被调用一次,同时验证最终返回的ResponseEntity状态码和body是否符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:45:12