Testing Secured Endpoints
Intermediate@WithMockUser fakes authentication for unit/integration tests. For realistic tests with JWTs, add the Authorization header manually. MockMvc with Spring Security support validates both authentication and authorization behavior.
Overview
Testing secured endpoints requires establishing an authenticated context. @WithMockUser creates a fake SecurityContext with configurable username and roles — fast but doesn't go through your actual filter chain. @WithUserDetails loads a real UserDetails via your UserDetailsService. For JWT-protected APIs, the most realistic approach is generating an actual token in the test and adding it as a header. Use @SpringBootTest + TestRestTemplate for full integration tests.
@WithMockUser and MockMvc Security
Spring Security Test autoconfigures MockMvc to process the SecurityFilterChain. @WithMockUser injects a fake principal — use it to test that authorization rules work correctly without going through the full login flow.
@WebMvcTest(CourseController.class)
class CourseControllerTest {
@Autowired MockMvc mockMvc;
// ── Authentication tests ────────────────────────────────────────────────
@Test
void unauthenticated_request_returns_401() throws Exception {
mockMvc.perform(get("/api/v1/courses/my-courses"))
.andExpect(status().isUnauthorized());
}
// @WithMockUser — creates SecurityContext with username="user", roles=["USER"]
@Test
@WithMockUser
void authenticated_user_can_access_their_courses() throws Exception {
mockMvc.perform(get("/api/v1/courses/my-courses"))
.andExpect(status().isOk());
}
// Custom role
@Test
@WithMockUser(roles = "ADMIN")
void admin_can_publish_course() throws Exception {
mockMvc.perform(post("/api/v1/admin/courses/{id}/publish", "course-123"))
.andExpect(status().isOk());
}
// Verify non-admin gets 403
@Test
@WithMockUser(roles = "USER")
void non_admin_gets_403_on_publish() throws Exception {
mockMvc.perform(post("/api/v1/admin/courses/{id}/publish", "course-123"))
.andExpect(status().isForbidden());
}
}Testing JWT-Protected Endpoints
For realistic integration tests with JWTs, generate a test token in @BeforeEach and pass it as the Authorization header. This exercises your full JWT filter chain.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
class JwtSecurityIntegrationTest {
@Autowired MockMvc mockMvc;
@Autowired JwtService jwtService;
@Autowired UserRepository userRepository;
private String userToken;
private String adminToken;
@BeforeEach
void setUp() {
// Create test users in the DB (test profile uses H2)
User user = userRepository.save(new User("test@example.com", Role.USER));
User admin = userRepository.save(new User("admin@example.com", Role.ADMIN));
userToken = jwtService.generateToken(user.getId());
adminToken = jwtService.generateToken(admin.getId());
}
@Test
void valid_jwt_grants_access() throws Exception {
mockMvc.perform(get("/api/v1/users/me")
.header("Authorization", "Bearer " + userToken))
.andExpect(status().isOk())
.andExpect(jsonPath("$.email").value("test@example.com"));
}
@Test
void expired_jwt_returns_401() throws Exception {
String expiredToken = jwtService.generateExpiredToken("user-id"); // test helper
mockMvc.perform(get("/api/v1/users/me")
.header("Authorization", "Bearer " + expiredToken))
.andExpect(status().isUnauthorized());
}
@Test
void user_cannot_access_admin_endpoint() throws Exception {
mockMvc.perform(post("/api/v1/admin/courses")
.header("Authorization", "Bearer " + userToken)
.contentType(MediaType.APPLICATION_JSON)
.content("{}"))
.andExpect(status().isForbidden());
}
}Key Points to Remember
- 1@WebMvcTest includes Spring Security by default — tests will fail with 401/403 without authentication setup.
- 2@WithMockUser is fast and simple — use it for authorization rule testing without touching your filter chain.
- 3@WithUserDetails uses your real UserDetailsService — more realistic but requires a real or mocked user in the DB.
- 4For JWT API testing, generate tokens in @BeforeEach and pass as Authorization: Bearer header.
- 5Test both the happy path (valid token, right role) AND the failure cases (no token, wrong role, expired token).
- 6Use @TestPropertySource or application-test.yml to override JWT secrets and expiry in test context.
Interview Questions
Sign in to ask AriaHow do you test a @PreAuthorize annotation on a service method?
What is the difference between @WithMockUser and @WithUserDetails?
Why does @WebMvcTest apply Spring Security by default?
How would you test that an expired JWT returns 401?
How do you test a multi-tenant endpoint that should return 403 for users from a different tenant?
Ask Aria about Testing Secured Endpoints
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.