Spring Boot: 3.5.4
The @FilterRegistrationBean
annotation is ignored in the auto-configured MockMvc
. Filters are registered, but with their default configuration.
This seems to be caused by SpringBootMockMvcBuilderCustomizer
using its own RegistrationBeanAdapter
implementation that is not aware of the @FilterRegistrationBean
annotation:
https://github.com/spring-projects/spring-boot/blob/925f9bc6ba99f0eaffce1e357282d3672b88e2a5/spring-boot-project/spring-boot-test-autoconfigure/src/main/java/org/springframework/boot/test/autoconfigure/web/servlet/SpringBootMockMvcBuilderCustomizer.java#L327-L336
Compare this with the "real" FilterRegistrationBeanAdapter
used during proper application start, which queries the BeanFactory
for the annotation and uses it to configure the filter:
https://github.com/spring-projects/spring-boot/blob/925f9bc6ba99f0eaffce1e357282d3672b88e2a5/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletContextInitializerBeans.java#L344-L355
Reproducer (extract and run mvnw test
): demo2.zip
The reproducer registers two filter beans.
The first one uses FilterRegistrationBean
directly and maps the filter to /classic
:
@Bean
FilterRegistrationBean<MyFilter> classicRegistrationBean() {
var f = new FilterRegistrationBean<>(new MyFilter("Classic"));
f.setUrlPatterns(List.of("/classic"));
return f;
}
The second one uses the annotation variant and maps the filter to /annotation
:
@Bean
@FilterRegistration(urlPatterns = "/annotation")
MyFilter annotationRegistrationBean() {
return new MyFilter("Annotation");
}
The filter itself adds the string given in the constructor to the matched-filter
response header.
A test than issues requests to /annotation
and /classic
and verifies that exactly one of the filters matched by asserting that the response header contains only Classic
or only Annotation
:
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.assertj.MockMvcTester;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@WebMvcTest
class Demo2ApplicationTests {
@Autowired
private MockMvcTester mvc;
@Test
void annotation() {
assertThat(mvc.get().uri("/annotation"))
.headers()
.containsEntry("matched-filter", List.of("Annotation")); // SUCCESS, because the non-annotation-based filter was configured correctly and, as expected, didn't apply here.
}
@Test
void classic() {
assertThat(mvc.get().uri("/classic"))
.headers()
.containsEntry("matched-filter", List.of("Classic")); // FAILURE, because the header _also_ contains "Annotation", even though we didn't expect the annotation-based filter to apply here.
}
}