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

为何Spock的setupSpec无法获取Spring Boot的@LocalServerPort?

问题原因及解决方案

核心差异原因

问题本质是Spock的类级生命周期与Spring的实例注入时机不匹配:

  • Spock中被@Shared修饰的字段属于类级共享变量,会在测试类的任何实例创建前完成初始化;setupSpec方法也是在所有测试方法执行前仅执行一次,且执行时测试类的实例还未被Spring容器初始化。
  • Spring的@LocalServerPort注解是针对测试类的实例字段进行注入的,只有当Spring容器完成测试实例的初始化后,才会把随机端口值注入到对应的实例字段中。

因此给localPort加上@Shared时,setupSpec执行时Spring还未完成端口注入,此时localPort为null;去掉@Shared后,localPort是实例字段,setup方法在每个测试实例初始化后执行,Spring已经完成端口注入,所以能拿到正确值。

优化解决方案(实现setupSpec中一次性初始化HttpClient)

如果想在setupSpec中只初始化一次HttpClient,可以通过Spring的Environment或ApplicationContext获取随机端口,避免依赖实例字段的注入:

@SpringBootTest(webEnvironment = RANDOM_PORT)
class BeerControllerOpenApiTest extends Specification {
    @Shared
    HttpClient beerClient

    @Shared
    @Autowired
    Environment environment

    void setupSpec() {
        // 从Environment中获取随机端口
        def localPort = environment.getProperty("local.server.port", Integer)
        beerClient = new HttpClient(baseUrl: "http://localhost:$localPort")
    }

    def "test list beers"() {
        when:
        def response = beerClient.get(
            path: "/api/v1/beer",
            headers: ["Content-Type": "application/json"]
        )

        then:
        response.statusCode == 200
    }
}

也可以直接注入ApplicationContext来获取端口:

@SpringBootTest(webEnvironment = RANDOM_PORT)
class BeerControllerOpenApiTest extends Specification {
    @Shared
    HttpClient beerClient

    @Shared
    @Autowired
    ApplicationContext applicationContext

    void setupSpec() {
        def localPort = applicationContext.environment.getProperty("local.server.port", Integer)
        beerClient = new HttpClient(baseUrl: "http://localhost:$localPort")
    }

    // 测试方法...
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 20:12:34