javaspringspring-bootspring-cloud-gatewayspring-cloud-contract

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


I have a spring cloud gateway application which routes requests to another service. Another service defines contracts which are imported as stubs by spring cloud gateway application in tests.

Now I would like to have contract tests in my gateway that will consume the stubs of another service. The problem is that I do not know how to inject the StubRunnerPort as property/environment so it can be picked by my configuration class and configure the routes accordingly :

Api gateway routes configuration

@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);
    }

}

And the test 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\"\"}"));
    }

Ideally I would like to have subscriptions.url property set after stub runner is configured but before my gateway configuration is picked by Spring so that url redirects will work.

I have already tried to use ApplicationContextInitializer but it seems that StubRunnerPort is not configured yet, when instance of initializer is launched.

So the question is - how to get stub runner port and use it to inject it into other services url, so that gateway would route the requests to the stub runner in tests?


Solution

  • Solution 1

    This can be achieved by using application-test.yml file with defined url property that makes use of substitution. application-test.yml file. The approach is described here :

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

    Here stubrunner.runningstubs.io.mkrzywanski.subscription-app.port will be available as Stub port so it can be substituted. No configuration changes are reuqired.

    Solution 2 (requires more code)

    I made it work by creating a test configuration which extends configuration that contains url properties and RouteLocator configuration and has a dependency on 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");
        }
    }
    

    The key points here are :

    The test looks like this now :

    @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());
        }
    }