mockJwt() WebTestClientConfigurer with MockMvcWebTestClient throws a NullPointerException.
Describe the bug
mockJwt() WebTestClientConfigurer does not seem to work with MockMvcWebTestClient that was introduced in Spring 5.3 as documented.
To Reproduce
@Test
void getMessagesWebTestClient() {
final WebTestClient testClient = MockMvcWebTestClient.bindTo(this.mockMvc)
.build();
testClient.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_message:read")))
.get()
.uri("/messages")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0]").isEqualTo("hello")
.jsonPath("$[1]").isEqualTo("world"); ;
}
throws the exception below
java.lang.NullPointerException: Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filter(org.springframework.web.server.WebFilter[])" because "httpHandlerBuilder" is null
at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$JwtMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:540)
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.demojwttest.MessageControllerTest.getMessagesWebTestClient(MessageControllerTest.java:51)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
while the equivalent following tests work
// MockMvc
@Test
void getMessages() throws Exception {
this.mockMvc.perform(get("/messages")
.with(jwt().authorities(new SimpleGrantedAuthority("SCOPE_message:read"))))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0]").value("hello"))
.andExpect(jsonPath("$[1]").value("world"));
}
// @WithMockUser
@Test
@WithMockUser(authorities = "SCOPE_message:read")
void getMessagesWebTestClientWithMockUser() {
final WebTestClient testClient = MockMvcWebTestClient.bindTo(this.mockMvc)
.build();
testClient.get()
.uri("/messages")
.exchange()
.expectStatus().isOk()
.expectBody()
.jsonPath("$[0]").isEqualTo("hello")
.jsonPath("$[1]").isEqualTo("world"); ;
}
Expected behavior
The first example works
Sample https://github.com/making/spring-security-gh-9257
While there may be a wider picture to consider, the basic reason for the NPE is because MockMvcWebTestClient.bindTo ultimately wires WebTestClient with a ClientHttpConnector instead of a WebHttpHandlerAdapter. Spring Security uses this adapter in order to introduce WebFilters into the mock client.
UPDATED: Fixed workaround, demo CSRF support workaround, and provide link to complete example.
@making Thanks for the report. We will look into a proper solution.
In the meantime, you can work around it using TestSecurityContextHolder.setAuthentication(Authentication). I put together a complete example in the gh-9257-webtestclient branch of my sample repository. You can see an excerpt below :
@SpringBootTest
@AutoConfigureMockMvc
public class WebTestClientTest {
WebTestClient client;
@MockBean
// mock the JwtDecoder so that the jwks is not resolved since no AuthZ Server Setup
JwtDecoder jwtDecoder;
// Override the CsrfTokenRepository. Must explicitly wire CsrfTokenRepository Bean into DSL for this to work
@MockBean
CsrfTokenRepository csrfTokenRepository;
DefaultCsrfToken csrf = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "123");
@Autowired
void setMockMvc(MockMvc mockMvc) {
this.client = MockMvcWebTestClient.bindTo(mockMvc)
.build();
}
@BeforeEach
void setupCsrf() {
given(this.csrfTokenRepository.generateToken(any())).willReturn(csrf);
}
private Consumer<HttpHeaders> csrf() {
return (headers) -> headers.set(csrf.getHeaderName(), csrf.getToken());
}
@Test
void getWhenAuthenticated() {
TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());
client
.get()
.uri("/")
.exchange()
.expectStatus().isOk();
}
@Test
void getWhenNotAuthenticated() {
client
.get()
.uri("/")
.exchange()
.expectStatus().is4xxClientError();
}
@Test
void csrfWhenNoToken() {
TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());
client
.post()
.uri("/")
.exchange()
.expectStatus().is4xxClientError();
}
@Test
void csrfWhenValidToken() {
TestSecurityContextHolder.setAuthentication(jwtAuthenticationToken());
client
.post()
.uri("/")
.headers(csrf())
.exchange()
.expectStatus().isOk();
}
private static JwtAuthenticationToken jwtAuthenticationToken() {
return new JwtAuthenticationToken(jwt().build(), AuthorityUtils.createAuthorityList("SCOPE_message:read"));
}
public static Jwt.Builder jwt() {
// @formatter:off
return Jwt.withTokenValue("token")
.header("alg", "none")
.audience(Arrays.asList("https://audience.example.org"))
.expiresAt(Instant.MAX)
.issuedAt(Instant.MIN)
.issuer("https://issuer.example.org")
.jti("jti")
.notBefore(Instant.MIN)
.subject("mock-test-subject");
// @formatter:on
}
}
@rstoyanchev Any ideas on how we can get Spring Security to integrate with the MockMvcWebTestClient support? With WebTestClient we typically add a WebFilter which can access the attribute on ServerWebExchange and that sets up the context, but it doesn't appear there is a way to do this when using MockMvcWebTestClient.
We need to be able to access the attribute on the ServerWebExchange that is populated by the mutateWith method and use it to setup the SecurityContext.
I think there is some misunderstanding. As of 5.3 WebTestClient can be used to exercise not only WebFlux but also WebMvc controllers with MockMvc as the server. In other words it's all MockMvc, and unrelated to WebFlux, and therefore any WebFlux related hooks do not apply.
For the most part what can be done directly with MockMvc can also be done via MockMvcWebTestClient. For example, you can configure and apply extension hooks to MockMvc just the same. However since it it a client, it is not as easy to modify individual requests with some special support via server side Filter. I can work with you more closely if we want to find a way to improve that.
For further reference the sections on WebTestClient and MockMvc have been updated to reflect this. You can also see all the framework samples tests for MockMvc ported to use with WebTestClient.
@making Thanks for the report. We will look into a proper solution. In the meantime, you can work around it using:
@Test void getMessagesWebTestClient() { TestingAuthenticationToken authentication = new TestingAuthenticationToken("a", "b", "SCOPE_message:read"); TestSecurityContextHolder.setAuthentication(authentication); final WebTestClient testClient = MockMvcWebTestClient.bindTo(this.mockMvc) .build(); testClient.mutateWith(mockJwt().authorities(new SimpleGrantedAuthority("SCOPE_message:read"))) .get() .uri("/messages") .exchange() .expectStatus().isOk() .expectBody() .jsonPath("$[0]").isEqualTo("hello") .jsonPath("$[1]").isEqualTo("world"); ; }
This still doesn't work for me.
Similar also happens in a test with @Autowired WebTestClient webTestClient and executing a webTestClient.mutateWith(csrf()).post()...:
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:265)
at org.springframework.test.web.reactive.server.DefaultWebTestClient.mutateWith(DefaultWebTestClient.java:167)
I'm experiencing the same behaviour than @membersound, do you have any workaround?
@gursahibsahni Sorry the workaround I posted previously, should have removed the mutateWith method. I've updated the example and provided a link to a repository that demonstrates the workaround in its entirety.
@soasada @membersound I've updated the sample above to demonstrate how to workaround CSRF test support not working too.
@rstoyanchev Thanks for the reply. I'd like to figure out a way that Spring Security users can use WebTestClient using the same APIs for WebMvc and WebFlux backends. It is confusing that mutateWith does not work for MockMvc based tests. What's more is there is currently no way to use many of Spring Security's test features when WebTestClient + MockMvc.
hi i have same issue with webTestClient and get null pointer exception i think because don't use jwt for webTestClient
and i solved this with add @AutoConfigureWebTestClient as class level annotation for integration test

The suggested workaround does not work with the current spring security versions (6.0.2), as csrf token handling has changed quite a bit. So far I was not able to create a working solution.
as @eiswind mentioned, I have the same issue with spring boot 3 and security 6 this is my test class:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@AutoConfigureWireMock(stubs = ["classpath:/mappings/authservice"], port = 0)
@TestPropertySource(
properties = [
"spring.flyway.enabled=false",
],
)
@Testcontainers
@ExtendWith(SpringExtension::class)
@ActiveProfiles("test")
@AutoConfigureWebTestClient
class ServiceIntegrationTest {
@Test
fun test() {
webTestClient.mutateWith(mockJwt().jwt(JwtUtils.generateJwt()))
.mutateWith(csrf())
.post()
.uri("/api/planning/stores/$businessUnitId/units")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isForbidden
}
}
and this is the error I am getting:
Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filter(org.springframework.web.server.WebFilter[])" because "httpHandlerBuilder" is null
java.lang.NullPointerException: Cannot invoke "org.springframework.web.server.adapter.WebHttpHandlerBuilder.filter(org.springframework.web.server.WebFilter[])" because "httpHandlerBuilder" is null
at org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers$JwtMutator.afterConfigurerAdded(SecurityMockServerConfigurers.java:540)
none of the workarounds seem effective on it
It's currently not possible to do this cleanly with MockMvc and using the mutateWith api because the functionality is in MockMvcHttpConnector and the WebTestClient.Builder does not expose a method to set a modified MockMvcHttpConnector as the ClientHttpConnector is final in the builder.
The best workaround currently if you still want to use WebTestClient instead of just using the MockMvc API is https://github.com/spring-projects/spring-security/issues/9304#issuecomment-841495717.
Some ideas
- The
WebTestClient.Builderneeds to expose a method to set theClientHttpConnectoror someClientHttpConnectorBuilder - The
MockMvcHttpConnectorneeds to store theRequestPostProcessorsto be applied on theMockHttpServletRequestBuilderand a means to create a newMockMvcHttpConnectorwith the existingRequestPostProcessorsand additional ones - The
MockMvcHttpConnectorneeds to apply theRequestPostProcessorsinadaptRequest.
As a workaround I wrote a simple builder which can be used with the org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.
Edit: Have edited the builder as when I was doing testing I realised that what I typically wanted to use was the filters customised by Spring Boot in the MockMvcBuilder and not binding to the WebApplicationContext with springSecurity.
package com.example;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import org.springframework.http.HttpMethod;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
import org.springframework.test.web.servlet.client.MockMvcWebTestClient;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.AbstractMockMvcBuilder;
import org.springframework.web.context.WebApplicationContext;
public class DefaultMockMvcWebTestClient implements WebTestClient {
private final Function<MockHttpServletRequestBuilder, WebTestClient> builder;
private final List<RequestPostProcessor> requestPostProcessors = new ArrayList<>();
public DefaultMockMvcWebTestClient(WebApplicationContext context) {
this.builder = (requestBuilder) -> MockMvcWebTestClient.bindToApplicationContext(context)
.apply(springSecurity()).defaultRequest(requestBuilder);
}
public DefaultMockMvcWebTestClient(AbstractMockMvcBuilder<?> mockMvcBuilder) {
this.builder = (requestBuilder) -> MockMvcWebTestClient
.bindTo(mockMvcBuilder.defaultRequest(requestBuilder).build()).build();
}
public DefaultMockMvcWebTestClient(Function<MockHttpServletRequestBuilder, WebTestClient> builder) {
this.builder = builder;
}
public DefaultMockMvcWebTestClient(DefaultMockMvcWebTestClient copy) {
this.builder = copy.builder;
this.requestPostProcessors.addAll(copy.requestPostProcessors);
}
public DefaultMockMvcWebTestClient with(RequestPostProcessor requestPostProcessor) {
this.requestPostProcessors.add(requestPostProcessor);
return this;
}
public DefaultMockMvcWebTestClient mutateWith(RequestPostProcessor requestPostProcessor) {
DefaultMockMvcWebTestClient copy = new DefaultMockMvcWebTestClient(this);
return copy.with(requestPostProcessor);
}
public WebTestClient build() {
MockHttpServletRequestBuilder requestBuilder = MockMvcRequestBuilders.get("/");
requestPostProcessors.stream().forEach(requestBuilder::with);
return this.builder.apply(requestBuilder);
}
@Override
public RequestHeadersUriSpec<?> get() {
return build().get();
}
@Override
public RequestHeadersUriSpec<?> head() {
return build().head();
}
@Override
public RequestBodyUriSpec post() {
return build().post();
}
@Override
public RequestBodyUriSpec put() {
return build().put();
}
@Override
public RequestBodyUriSpec patch() {
return build().patch();
}
@Override
public RequestHeadersUriSpec<?> delete() {
return build().delete();
}
@Override
public RequestHeadersUriSpec<?> options() {
return build().options();
}
@Override
public RequestBodyUriSpec method(HttpMethod method) {
return build().method(method);
}
@Override
public Builder mutate() {
return build().mutate();
}
@Override
public WebTestClient mutateWith(WebTestClientConfigurer configurer) {
return build().mutateWith(configurer);
}
}
It can be used something like
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;
public class MyTest {
DefaultMockMvcWebTestClient webTestClient;
@Autowired
public void setupClient(AbstractMockMvcBuilder<?> mockMvcBuilder) {
this.webTestClient = new DefaultMockMvcWebTestClient(mockMvcBuilder);
}
@Test
public void test() {
this.webTestClient.mutateWith(oidcLogin()).get() // ...
}
// ...
}
@rwinch as per my https://github.com/spring-projects/spring-framework/pull/30233#pullrequestreview-1637375454, I think we could do something with the WebTestClient#mutate(WebTestClientConfigurer) hook. For WebFlux use, I believe Spring Security is using the WebHttpHandlerBuilder argument to modify the server. For WebMvc use, it would use the ClientHttpConnector argument which would be the MockMvcClientHttpConnector. We'll need to make some changes in Spring Framework to allow MockMvcClientHttpConnector to be mutated and that should make it possible for Spring Security to address this issue.
Any target date for the final solution of this issue? We also suffer in this issue when using WebTestClient to test Spring MVC after involved security.
@WebMvcTest(controllers = XxxController.class)
@WithMockUser
class XxxControllerTest {
@Autowired
private MockMvc mockMvc;
private WebTestClient webTestClient;
@BeforeEach
void setupWebClient() {
webTestClient = MockMvcWebTestClient
.bindTo(mockMvc)
.build()
// will encounter NPE if -> .mutateWith(csrf())
;
}
hi i have same issue with webTestClient and get null pointer exception i think because don't use jwt for webTestClient and i solved this with add @AutoConfigureWebTestClient as class level annotation for integration test
Could you provide a source code? I think that @AutoConfigureWebTestClient should not have an impact on the configuration of webtestclient if SpringBootTest is used with RANDOM_PORT
We just worked our way through this same rabbit hole at my job and came up with a solution that works in the latest versions.
For us, the key pieces were:
- Configure your own
WebTestClientin the way described below - do not rely on theWebTestClientthat is auto-configured with@SpringBootTest(webEnvironment = RANDOM_PORT)or similar - Use the Spring Security test context annotations to mock the security context - do not try to use
.mutateWithon theWebTestClient
Set your WebTestClient up like this in your test class:
private WebTestClient webTestClient;
@Autowired
public void setWebApplicationContext(final WebApplicationContext context) {
webTestClient = MockMvcWebTestClient
.bindToApplicationContext(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
this portion of the solution was found in https://github.com/spring-projects/spring-security/issues/9304#issuecomment-841495717
Then, you can annotate your test classes / methods with @WithMockUser or any custom annotation created with @MockSecurityContext + a corresponding implementation of WithSecurityContextFactory (see https://docs.spring.io/spring-security/reference/servlet/test/method.html#test-method-withsecuritycontext)
Full working test class calling a controller that echoes the principal's name:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class WebTestClientMockUserTest {
private WebTestClient webTestClient;
@Autowired
public void setWebApplicationContext(final WebApplicationContext context) {
webTestClient = MockMvcWebTestClient
.bindToApplicationContext(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
@Test
@WithMockUser("Austin")
void testWithMockUser() {
webTestClient
.get()
.uri("/whoami")
.exchange()
.expectStatus()
.isOk()
.expectBody(String.class)
.isEqualTo("You are Austin");
}
}
I think the thing that is causing everyone the most trouble is that you need to mock the security in a WebMVC-first way, not the WebFlux-first way exposed on WebTestClient.mutateWith(), which as all the above comments point out is only designed to operate on the reactive security stack.