Home/Learn/Spring Boot/Testing Secured Endpoints

Testing Secured Endpoints

Intermediate
Security

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

Java — @WithMockUser with MockMvc security assertions
@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.

Java — integration test with real JWT token in Authorization header
@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 Aria
1

How do you test a @PreAuthorize annotation on a service method?

MediumThoughtWorks
2

What is the difference between @WithMockUser and @WithUserDetails?

MediumAmazon
3

Why does @WebMvcTest apply Spring Security by default?

EasyAtlassian
4

How would you test that an expired JWT returns 401?

MediumRazorpay
5

How do you test a multi-tenant endpoint that should return 403 for users from a different tenant?

HardGoldman Sachs

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.

Loading discussion…