Logo

dev-resources.site

for different kinds of informations.

Mastering Spring Cloud Gateway Testing: Filters (part 2)

Published at
4/24/2024
Categories
spring
testing
spock
gateway
Author
mohammedalics
Categories
4 categories in total
spring
open
testing
open
spock
open
gateway
open
Author
13 person written this
mohammedalics
open
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!

gateway Article's
30 articles in total
Favicon
Load balancer vs Gateway vs reverse proxy vs forward proxy
Favicon
Configurable Kong API Gateway with Micronaut Services in Kotlin — A very odd Yucca tribute concert
Favicon
Announcing the MagicAPI AI Gateway: The Fastest AI Proxy for Developers!
Favicon
Mastering AWS Gateway Load Balancer: A Comprehensive Guide
Favicon
Unlocking Azure: Your Gateway to the Cloud
Favicon
Microservices: Set Up a Gateway with UI (Thymeleaf)
Favicon
Instructions for Installing Interactive Brokers IB Gateway in Linux Bash
Favicon
Mastering LLM API Gateway: Your Ultimate Guide
Favicon
Kong Gateway - Validando configurações específicas para exposição de serviços
Favicon
Jasmin sms gateway
Favicon
Why Banks in India, ME and Africa Need a Future-Ready Payments Platform
Favicon
Mastering Spring Cloud Gateway Testing: Filters (part 2)
Favicon
Mastering Spring Cloud Gateway Testing: Predicates (part 1)
Favicon
Best Practices for Configuring Rate Limits to Prevent DDoS
Favicon
Optimizing Kubernetes API Gateway for High Traffic Volumes
Favicon
Enterprise Service Bus (ESB) vs. API Gateway in Modern IT Architecture
Favicon
Kubernetes Egress Gateway
Favicon
GatewayD: The Case For A Database Gateway
Favicon
GraphQL Tools Transformations
Favicon
Chaining API requests with API Gateway
Favicon
Meson Case Study: Arweave Gateway
Favicon
Ocelot & files download
Favicon
What's Carrier Gateway is AWS?
Favicon
Spring Cloud Gateway
Favicon
What is an Egress only internet gateways in AWS?
Favicon
What is an Internet Gateway in AWS?
Favicon
Kusk + Cloudentity - Fine-Grained Authorization for your APIs
Favicon
Kusk Gateway 1.2.0 Release - OAuth, Local Mocking and more!
Favicon
Deflector and shield
Favicon
5 minutes to add RESTful APIs for your gRPC services

Featured ones: