javaspringspring-bootunit-testing

Spring Cloud Gateway tests fail after upgrade to Spring Boot 3.4.0 (specifically spring-web 6.2.0)


After upgrading to Spring Boot 3.4.0 (Spring Web 6.2.0), my Gateway filter tests started failing. The tests previously verified that request headers were modified correctly, but now the modifications don't seem to be visible in the test assertions.

Here's a simplified example that used to pass but now fails:

@Component
class TestGatewayFilter implements GatewayFilter {
    public TestGatewayFilter() {
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Builder builder = exchange.getRequest().mutate();
        builder.header("test-header", "test");
        return chain.filter(exchange.mutate().request(builder.build()).build());

    }
}

@ExtendWith(MockitoExtension.class)
class TestGatewayFilterTest {

    @InjectMocks
    private TestGatewayFilter testGatewayFilter;

    @Test
    void shouldRunFilter() {
        MockServerHttpRequest mockRequest = MockServerHttpRequest
                .get("/testfilter")
                .build();
        MockServerWebExchange exchange = MockServerWebExchange.from(mockRequest);

        Mono<Void> result = testGatewayFilter.filter(exchange, e -> Mono.empty());
        StepVerifier.create(result)
                .verifyComplete();

        assertEquals("test", exchange.getRequest().getHeaders().getFirst("test-header"));
    }
}

Dependencies:


<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.4.0</version>
</parent>

<properties>
    <java.version>17</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>io.projectreactor</groupId>
        <artifactId>reactor-test</artifactId>
        <version>3.7.1</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>1.13.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-gateway-dependencies</artifactId>
            <version>4.2.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

The test passes with Spring Boot 3.3.x (which includes Spring Web 6.1.x) but fails with 3.4.0 (which includes Spring web 6.2.x). I've looked through the Spring Framework 6.2 release notes but couldn't find any mention of changes to ServerWebExchange or MockServerWebExchange behavior.

I'd like to test whether other services are interacted with in this unit test too. Questions:

  1. Is this a known breaking change?
  2. What's the correct way to verify request headers and interacted services in Gateway filter tests now?
  3. If this is unintended behavior, where should I report it?

Solution

  • The assertion is performed on the wrong exchange: the original one instead of the mutated one. I'm not sure why this worked previously, maybe a Spring Cloud Gateway behavior change?

    In any case, the test should be written like this:

        @Test
        void shouldRunFilter() {
            MockServerHttpRequest mockRequest = MockServerHttpRequest
                    .get("/testfilter")
                    .build();
            MockServerWebExchange exchange = MockServerWebExchange.from(mockRequest);
    
            Mono<Void> result = testGatewayFilter.filter(exchange, e -> {
                assertEquals("test", e.getRequest().getHeaders().getFirst("test-header"));
                return Mono.empty();
            });
            StepVerifier.create(result).verifyComplete();
        }