spring-bootkotlinunit-testingspring-testmockmvc

How to associate the mockmvc user to the security context holder


I am having trouble with not being able to associate the user I am passing into the mockmvc get request to the security context holder so I am able to create a mock principle.

@Import(SecurityConfig::class)
@WebMvcTest(controllers = [MovieController::class])
class MovieControllerTests {
    private lateinit var mockMvc: MockMvc

    @MockBean
    private lateinit var accountRepository: AccountRepository

    @MockBean
    private lateinit var movieRepository: MovieRepository

    @BeforeEach
    fun setup() {
        mockMvc = MockMvcBuilders.standaloneSetup(MovieController(movieRepository, accountRepository))
            .apply<StandaloneMockMvcBuilder?>(SecurityMockMvcConfigurers.springSecurity()).build()
    }
    @Test
    fun `does return movie queue`() {
        mockMvc.get("/movies/fetch-queue") {
            with(user("donna"))
        }.andExpectAll {
            status {
                isOk()
            }
            content {
                contentType(MediaType.APPLICATION_JSON)
            }
        }
        Mockito.verify(accountRepository).findAccountByUsername("donna")
    }
}

I have read the documentation that says I need to manually add the securitycontextpersistencefilter to the mockmvc instance, however I don't know how exactly to do that. The documentation also says that mocking users only works by associating the user with the HttpServletRequest.


Solution

  • The simplest way to modify the SecurityContext in tests is by using @WithMockUser. This annotation allows you to set the username, roles, and authorities directly.

    @Test
    @WithMockUser(username = "testuser", roles = {"USER"})
    public void testWithMockUser() {
        // Test code here
    }
    

    Create a Custom Security Context with @WithSecurityContext If you need more advanced customization (e.g., setting specific authentication details), you can create a custom annotation and implement a SecurityContextFactory.

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @WithSecurityContext(factory = CustomSecurityContextFactory.class)
    public @interface WithCustomUser {
        String username() default "customuser";
        String[] roles() default {"USER"};
    }
    

    Implement the Security Context Factory

    public class CustomSecurityContextFactory implements WithSecurityContextFactory<WithCustomUser> {
    
    @Override
    public SecurityContext createSecurityContext(WithCustomUser customUser) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
    
        List<GrantedAuthority> authorities = Arrays.stream(customUser.roles())
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());
    
        Authentication auth = new UsernamePasswordAuthenticationToken(
            customUser.username(), "password", authorities);
        context.setAuthentication(auth);
    
        return context;
    }
    }
    

    Indeed, Spring Security uses a ThreadLocal-based SecurityContextHolder to store and retrieve the SecurityContext during tests and runtime. This approach ensures that the security context is scoped to the current thread. Take a look here for more details

    org.springframework.security.test.context.TestSecurityContextHolder

    This ThreadLocal strategy in Spring Security is limited for advanced use cases like propagating the security context to child threads or handling reactive and asynchronous scenarios; however, alternatives like InheritableThreadLocal or custom strategies can address these limitations.