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