如何在 spring-cloud-gateway 合同测试中使用 spring-cloud-contract 的 StubRunner 端口设置 url

How to set urls with port of StubRunner from spring-cloud-contract in spring-cloud-gateway contract tests

我有一个 spring 云网关应用程序,可以将请求路由到另一个服务。另一项服务定义了在测试中由 spring 云网关应用程序作为存根导入的合同。

现在我想在我的网关中进行合同测试,以使用另一个服务的存根。问题是我不知道如何将 StubRunnerPort 注入为 property/environment 所以它可以被我的配置 class 选中并相应地配置路由:

Api网关路由配置

@Configuration
class GatewayConfig {

    
    @Value("${subscriptions.url}")
    private String subscriptionsUrl;

    @Autowired
    private TokenRelayGatewayFilterFactory tokenFilterFactory;

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http.csrf(ServerHttpSecurity.CsrfSpec::disable);
        return http.build();
    }

    @Bean
    RouteLocator routeLocator(final RouteLocatorBuilder routeLocatorBuilder) {
        return routeLocatorBuilder.routes()
                .route("subscriptions", subscriptionsRoute())
                .build();
    }

    private Function<PredicateSpec, Buildable<Route>> subscriptionsRoute() {
        return spec -> spec
                .path("/subscriptions/**")
                .filters(s -> s.filter(tokenFilterFactory.apply()).prefixPath("/v1"))
                .uri(subscriptionsUrl);
    }

}

测试 class :

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {PnApiGatewayApp.class})
@AutoConfigureStubRunner(ids = "io.mkrzywanski:subscription-app:+:stubs", stubsMode = StubRunnerProperties.StubsMode.CLASSPATH)
@ActiveProfiles("test")
class SubscriptionSpec {

    private WebTestClient webClient;

    @LocalServerPort
    private int port;

    @StubRunnerPort("io.mkrzywanski:subscription-app")
    private int stubRunnerPort;

    @Autowired
    ConfigurableEnvironment environment;

    @BeforeEach
    void setup() {
        String baseUri = "http://localhost:" + port;
        this.webClient = WebTestClient.bindToServer()
                .responseTimeout(Duration.ofSeconds(10))
                .baseUrl(baseUri).build();
    }

    @Test
    void  test() {
        String body = "{\"userId\":\"22e90bbd-7399-468a-9b76-cf050ff16c63\",\"itemSet\":[{\"value\":\" Rainbow Six\"}]}";
        var response = webClient.post()
                .uri("/subscriptions")
                .header("Authorization", "Bearer xxx")
                .header("Content-type", MediaType.APPLICATION_JSON_VALUE)
                .bodyValue(body)
                .exchange()
                .expectStatus().isCreated()
                .expectBody(String.class)
                .value(Matchers.equalTo("{\"subscriptionId : \"6d692849-58fd-439b-bb2c-50a5d3669fa9\"\"}"));
    }

理想情况下,我希望 subscriptions.url 属性 设置 after stub runner 已配置,但 before 我的网关配置由 Spring 选择,因此 url 重定向将起作用。

我已经尝试使用 ApplicationContextInitializer 但似乎 StubRunnerPort 尚未配置,当启动初始化程序实例时。

所以问题是 - 如何获取存根运行器端口并使用它将其注入其他服务url,以便网关将请求路由到测试中的存根运行器?

解决方案 1

这可以通过使用 application-test.yml 文件和定义 url 属性 的文件来实现,该文件使用替换。 application-test.yml 文件。该方法描述为 here :

subscriptions:
  url: http://localhost:${stubrunner.runningstubs.io.mkrzywanski.subscription-app.port}

这里 stubrunner.runningstubs.io.mkrzywanski.subscription-app.port 将作为 Stub 端口可用,因此可以被替换。不需要更改配置。

解决方案 2(需要更多代码)

我通过创建一个测试配置使其工作,该配置扩展了包含 url 属性和 RouteLocator 配置并且依赖于 batchStubRunner bean 的配置:

@DependsOn("batchStubRunner")
@EnableAutoConfiguration
@Import(LoggingFilter.class)
class GatewayTestConfig extends GatewayConfig implements InitializingBean {

    @Autowired
    ConfigurableEnvironment environment;

    @Override
    public void afterPropertiesSet() {
        this.subscriptionsUrl = "http://localhost:" + environment.getProperty("stubrunner.runningstubs.io.mkrzywanski.subscription-app.port");
    }
}

这里的重点是:

  • 只有在 batchStubRunner bean 可用后配置才 运行 因此可以在 environment
  • 中找到 StrubRunner 的端口
  • 配置实现了 InitializingBean,所以我可以覆盖现在 protected 在父配置
  • 中的 subscriptionsUrl
  • 覆盖 subscriptionsUrl 后 - 它可用于从父配置配置 RouteLocator bean。

测试现在看起来像这样:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = {GatewayTestConfig.class})
@AutoConfigureStubRunner(ids = "io.mkrzywanski:subscription-app:+:stubs", stubsMode = StubRunnerProperties.StubsMode.CLASSPATH)
@ActiveProfiles("test")
class SubscriptionSpec {

    private WebTestClient webClient;

    @LocalServerPort
    private int port;

    @BeforeEach
    void setup() {
        String baseUri = "http://localhost:" + port;
        this.webClient = WebTestClient.bindToServer()
                .responseTimeout(Duration.ofSeconds(10))
                .baseUrl(baseUri).build();
    }

    @Test
    void shouldRouteToSubscriptions() {

        String body = "{\"userId\":\"22e90bbd-7399-468a-9b76-cf050ff16c63\",\"itemSet\":[{\"value\":\"Rainbow Six\"}]}";
        webClient.post()
                .uri("/subscriptions")
                .header("Accept", MediaType.APPLICATION_JSON_VALUE)
                .header("Authorization", "Bearer xxx")
                .header("Content-type", MediaType.APPLICATION_JSON_VALUE)
                .bodyValue(body)
                .exchange()
                .expectStatus().isCreated()
                .expectBody()
                .jsonPath("$.subscriptionId").exists()
                .jsonPath("$.subscriptionId").value(IsUUID.UUID());
    }
}