Describe the bug According to Spring Framework 5.3 Reference Documentation, I should be able to use WebTestClient to test Spring MVC applications. I tried it and it does not work if you have something reactive on classpath.

Caused by: java.lang.NullPointerException
    at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$CsrfMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:259)
    at org.springframework.test.web.reactive.server.DefaultWebTestClientBuilder.apply(DefaultWebTestClientBuilder.java:247)
    at org.springframework.test.web.reactive.server.DefaultWebTestClient.mutateWith(DefaultWebTestClient.java:160)
    at com.example.mvcwebtestclient.GreetingControllerTest.create(GreetingControllerTest.java:24)

The code in SecurityMockServerConfigurers (spring-security-test 5.4.2) is clearly buggy: it accesses a nullable argument httpHandlerBuilder:

        public void afterConfigurerAdded(WebTestClient.Builder builder,
                @Nullable WebHttpHandlerBuilder httpHandlerBuilder, @Nullable ClientHttpConnector connector) {
            CsrfWebFilter filter = new CsrfWebFilter();
            filter.setRequireCsrfProtectionMatcher((e) -> ServerWebExchangeMatcher.MatchResult.notMatch());
            httpHandlerBuilder.filters((filters) -> filters.add(0, filter));
        }

Sample

build.gradle

dependencies {
    implementation(platform("org.springframework.boot:spring-boot-dependencies:2.4.1"))
    implementation 'org.springframework.boot:spring-boot-starter-data-mongodb-reactive'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-webflux' // result of spring-framework#26308
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
    testImplementation 'org.springframework.security:spring-security-test'
}

GreetingController.java

@RestController
@RequestMapping("/greetings")
public class GreetingController {

    @GetMapping
    public Flux<String> findAll() {
        return Flux.just("Hello", "world!");
    }
}

GreetingControllerTest.java

@SpringBootTest
class GreetingControllerTest {

    private WebTestClient client;

    @Autowired
    public void create(WebApplicationContext context) {
        client = MockMvcWebTestClient.bindToApplicationContext(context)
                .apply(springSecurity())
                .configureClient()
                .build()
                .mutateWith(csrf());
    }

    @Test
    public void findsAll() {
        client.get().uri("/greetings")
                .exchange()
                .expectStatus().isOk();
    }
}

Comment From: jzheaux

This appears to be a duplicate of https://github.com/spring-projects/spring-security/issues/9257

Comment From: archi-hub-85

As the original problem hasn't been fixed yet, I've found this workaround:

    @Autowired
    public void create(WebApplicationContext context) {
        client = MockMvcWebTestClient.bindToApplicationContext(context)
                .apply(springSecurity())
                .defaultRequest(MockMvcRequestBuilders.get("/").with(SecurityMockMvcRequestPostProcessors.csrf())
                .configureClient()
                .build();
    }

The used method for MockMvcRequestBuilders and URL template don't really matter, because they will be overridden in test methods.

Comment From: justin-tay

Just to add on to the original workaround. In my case I was using Spring Boot and what I typically wanted was to use the filters customised by Spring Boot on the MockMvcBuilder.

    @Autowired
    public void create(AbstractMockMvcBuilder<?> mockMvcBuilder) {
        MockMvc mockMvc = mockMvcBuilder
                .defaultRequest(MockMvcRequestBuilders.get("/").with(SecurityMockMvcRequestPostProcessors.csrf()))
                .build();
        client = MockMvcWebTestClient.bindTo(mockMvc).build();
    }