DEV Community

Cover image for Mastering Spring Cloud Gateway Testing: Filters (part 2)
Mohammed Ammer
Mohammed Ammer

Posted on

Mastering Spring Cloud Gateway Testing: Filters (part 2)

In Part 1, we dived into testing predicates efficiently. Now, let's continue in the same vein but shift our focus to filters. Just like predicates, filters play a crucial role in Spring Cloud Gateway routing by making changes to the request or the target backend.

In this post, we'll delve into writing effective integration tests for filters. These tests ensure that our custom filters perform precisely as intended, validating that they are accomplishing the specific tasks they were designed for.

Before going forward, I strongly recommend you go through Part 1 to leverage main concepts about Spring Cloud Gateway routing if you doubt about it.

Let us go!

For our demonstration, we've created AddLogisticProvidersHeaderGatewayFilterFactory. This function adds a new header containing shipping company information based on the client's IP location. That's all there is to it.

@Component
class AddLogisticProvidersHeaderGatewayFilterFactory(val countryService: CountryService, val logisticService: LogisticService) :
    AbstractGatewayFilterFactory<AddLogisticProvidersHeaderGatewayFilterFactory.Config>(
        Config::class.java
    ) {

    override fun apply(config: Config): GatewayFilter {
        return GatewayFilter { exchange, chain ->
            // Get the client IP address from the request
            val clientIP = exchange.request.headers.getFirst("X-Forwarded-For")
            val country = clientIP?.let { countryService.getCountry(it) }
            val shippingCompanies = country?.let { logisticService.getProviders(it) }
            shippingCompanies?.let {
                val request = exchange.request.mutate().header(config.headerName, it.joinToString(",")).build()
                return@GatewayFilter chain.filter(exchange.mutate().request(request).build())
            }
            chain.filter(exchange)

        }
    }

    class Config(
        val headerName: String,
    )
}
Enter fullscreen mode Exit fullscreen mode

Testing strategy

Filters serve as individual units of logic that can modify both the request and/or response. Using our custom filter as an example, we anticipate modifying the request by adding a new header with calculated values.

By establishing an integration test specific to this filter, wherein we send various types of requests and validate whether the filter produces the expected output by adding or omitting the new header, we can ensure that the filter is functioning as intended.

Configuration

Based on the description above, here is how the Spring Cloud Gateway configuration for our integration test may look:

spring:
  cloud:
    gateway:
      routes:
        - id: route_all_to_oauth
          uri: http://localhost:${wiremock.server.port}
          predicates:
            - Path=/**
          filters:
            - name: AddLogisticProvidersHeader
              args:
                headerName: X-Shipping-Providers
Enter fullscreen mode Exit fullscreen mode

In this configuration snippet, we use predicates to match all requests (as the specifics of the request aren't relevant for this test). However, in the filters section, we configure a single filter with the necessary settings. For the AddLogisticProvidersHeader filter, we specify the header name as X-Shipping-Providers.

Integration test

Similar to the predicates integration test, I utilized the Spock framework to handle my integration test. Below is the Integration Specification for the AddLogisticProvidersHeader filter.

@ActiveProfiles("AddLogisticProvidersHeaderGatewayFilterFactoryIntegrationSpec")
class AddLogisticProvidersHeaderGatewayFilterFactoryIntegrationSpec extends AbstractIntegrationSpec {


    public static final String SHIPPING_COMPANIES_RESULT = "shippingCompaniesResult"

    @Unroll
    def "given request from #country by ip #ip, expected shipping providers header existence #expectedHeaderExistence and expected header value #expectedHeaderValue"() {
        given:

        wireMockServer.stubFor(
                any(anyUrl()).atPriority(1)
                        .withHeader("X-Shipping-Providers", matching(".*"))
                        .willReturn(aResponse().withStatus(200).with {
                            if (expectedHeaderExistence) {
                                it.withHeader(SHIPPING_COMPANIES_RESULT, expectedHeaderValue)
                            }
                            return it
                        }
                        )
        )

        wireMockServer.stubFor(
                any(anyUrl()).atPriority(2)
                        .willReturn(aResponse().withStatus(200))
        )

        when:
        def request = webTestClient.get().uri {
            it.path("/")
            it.build()
        }
        if (ip != null) {
            request.header("X-Forwarded-For", ip)
        }

        def result = request.exchange()

        then:
        if (expectedHeaderExistence) {
            result.expectHeader().valueEquals(SHIPPING_COMPANIES_RESULT, expectedHeaderValue)
        } else {
            result.expectHeader().doesNotExist(SHIPPING_COMPANIES_RESULT)
        }

        where:
        country   | ip              | expectedHeaderExistence | expectedHeaderValue
        "France"  | "103.232.172.0" | true                    | "HERMES"
        "Germany" | "77.21.147.170" | true                    | "DHL,HERMES"
        "USA"     | "30.0.0.0"      | false                   | null
    }

}
Enter fullscreen mode Exit fullscreen mode

In this test, we mock any request that has the header X-Shipping-Providers to return a header containing the same shipping companies as those added to the request header.

Subsequently, we validate whether the header was added to the request by checking for the existence of the header and confirming that it contains the expected value.

To try out the example by yourself, be sure to check out the accompanying GitHub repository here.

I hope you found it useful!

Top comments (0)