I am testing/using the new RestTestClient with Spring Boot 4 and I wanted to test a controller method if a role is not matching. But instead of status code 403 (or 401 if no credentials are given) the returning status code is 500 (Internal Server Error).
I figured out that org.springframework.test.web.servlet.client.MockMvcClientHttpRequestFactory.MockMvcClientHttpRequest on line 100/101 is the cause for the code 500. (Version Spring Framework 7.0.1)
Is this an intended behavior or does it need to be improved?
What I expect
Even with SpringBootTest.WebEnvironment.MOCK I want the same http status code on security exceptions like on a real http request.
Here is some example code:
Controller
@RestController
@RequestMapping("/api")
public class OneController {
// Instead of 401 or 403 the @PreAuthorize returns 500 to the test result if not matching
@GetMapping("/one")
@PreAuthorize( "hasRole('ADMIN')")
public String getOne() {
return "One";
}
// This is returning StatusCode 402 to the test expectation
@GetMapping("/two")
public String getTwo() {
throw new ResponseStatusException(HttpStatus.PAYMENT_REQUIRED);
}
}
Test-Class
@SpringBootTest
@AutoConfigureRestTestClient
public class OneControllerTest {
@Test
void testWrongRole(@Autowired RestTestClient client) {
Authentication authentication = new AnonymousAuthenticationToken(
"key", "admin", List.of(new SimpleGrantedAuthority("USER")) // ADMIN is expected
);
SecurityContextHolder.getContext().setAuthentication(authentication);
// With none matching @PreAuthorize on the controller method,
// the test will fail with status 500 instead of expected code 403
client.get().uri("/api/one")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isEqualTo(HttpStatus.FORBIDDEN);
}
@Test
void testStatusException(@Autowired RestTestClient client) {
// 402 is matching the thrown exception from the controller method
client.get().uri("/api/two")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isEqualTo(HttpStatus.PAYMENT_REQUIRED);
}
}
Comment From: nigtrifork
Hi @hfrahmann
I had a similar issue today, trying out the RestTestClient with Spring Security. I ended up registering a RestTestClient bean myself like this:
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers;
import org.springframework.test.web.servlet.client.RestTestClient;
import org.springframework.web.context.WebApplicationContext;
@TestConfiguration
public class RestTestClientConfiguration {
@Bean
RestTestClient restTestClient(WebApplicationContext webApplicationContext) {
return RestTestClient.bindToApplicationContext(webApplicationContext)
.configureServer(mockMvc -> mockMvc.apply(SecurityMockMvcConfigurers.springSecurity()))
.build();
}
}
and then replacing @AutoConfigureRestTestClient on the test class with @Import(RestTestClientConfiguration.class).
I hope this helps :)
Comment From: hfrahmann
Thank you, @nigtrifork 🙂
I digged a little bit deeper and I've found the Interface RestTestClientBuilderCustomizer.
With that you can still use the @ AutoConfigureRestTestClient and use your code with configureServer.
Just add this class to the test scope.
@Component
public class MyRestTestClientBuilderCustomizer implements RestTestClientBuilderCustomizer {
@Override
public void customize(RestTestClient.Builder<?> builder) {
if (builder instanceof RestTestClient.WebAppContextSetupBuilder webAppContextSetupBuilder) {
webAppContextSetupBuilder.configureServer(
mockMvc -> mockMvc.apply(SecurityMockMvcConfigurers.springSecurity())
);
}
}
}
Comment From: nigtrifork
@hfrahmann , that's even better. I will adopt this too!
Comment From: bclozel
That's strange. This should be done automatically for you.
Do you have the spring-boot-starter-security-test dependency on your classpath? See the Spring Boot 4.0 upgrade guide.
Comment From: hendrik-frahmann-nmc
I just added this dependency but it's still not working.
Here is a dependency tree from our test-lib test-lib-dependencytree.txt
Edit: oops, commented with my company account
Comment From: hendrik-frahmann-nmc
I have created a demo project including the workaround customizer.
https://github.com/hendrik-frahmann-nmc/spring-boot-4-security-test-demo
Comment From: hfrahmann
It seems like that this problem should be solved in spring-boot not in framework. So I close this issue if it's ok.
Comment From: bclozel
I could have transferred this issue after debugging the problem.
Comment From: bclozel
For reference, the solution is here: https://github.com/spring-projects/spring-boot/issues/48316#issuecomment-3587052488