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.
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.