Issue
I'm writing some integration tests for my Spring MVC Controller.
The controllers are secured by Spring Security.
This is the test class I currently have:
@SpringBootTest(
webEnvironment = SpringBootTest.WebEnvironment.MOCK,
classes = GuiBackendApplication.class
)
@AutoConfigureMockMvc
public class ConfigEditorControllerIntegrationTest {
@Autowired
private MockMvc mockedMvc;
@Test
@WithMockUser(username = "user", password = "password", roles = {"admin"})
public void adminsCanAccessRuntimeConfig() throws Exception {
this.mockedMvc.perform(get("/my/custom/api"))
.andExpect(status().isOk());
}
}
This test class ensures that admins can access my endpoint. It works fine.
BUT what if I want to test if ONLY users with the admin role can access my endpoint?
I could write a test that uses @WithMockUsers with all the roles I currently have except the admin role. But that would me awful to maintain. I want my test to ensure that only users with the admin role can access my endpoint, regardless of any new roles.
I checked the Spring Reference Docs and didn't find anything about that. Is there a way to achieve that?
Something like this
@Test
@WithMockUser(username = "user", password = "password", roles = {"IS NOT admin"})
public void nonAdminsCannotAccessRuntimeConfig() throws Exception {
this.mockedMvc.perform(get("/my/custom/api"))
.andExpect(status().isUnauthorized());
}
Solution
Spring Security does not know what roles does your system define. So you have to tell it and test it one by one if you want to have 100% test coverage for all the available roles.
You can do it easily and in a maintenance way by using JUnit 5 's @ParameterizedTest
and configuring MockMvc
with the UserRequestPostProcessor
with different roles.
Something like :
public class ConfigEditorControllerIntegrationTest {
@ParameterizedTest
@MethodSource
public void nonAdminsCannotAccessRuntimeConfig(String role) throws Exception {
mockedMvc.perform(get("/my/custom/api")
.with(user("someUser").roles(role)))
.andExpect(status().isUnauthorized());
}
static List<String> nonAdminsCannotAccessRuntimeConfig() {
return Roles.exclude("admin");
}
}
And create a class to maintain all the available roles :
public class Roles {
public static List<String> all() {
return List.of("admin", "hr", "developer" , "accountant" , .... , "devops");
}
public static List<String> exclude(String excludeRole) {
List<String> result = new ArrayList<>(all());
result.remove(excludeRole);
return result;
}
}
Answered By - Ken Chan
Answer Checked By - David Marino (JavaFixing Volunteer)