Testing Conditional Links with @WithMockUser in Spring Security
Hypermedia APIs provide not just data but also actions that users can take, often exposed as conditional links or affordances. In Spring applications secured by Spring Security, it’s common to tailor these hypermedia controls based on the currently authenticated user’s role or privileges.
To ensure this dynamic behavior works correctly, especially in testing, Spring offers the powerful @WithMockUser annotation. It allows you to simulate authenticated requests and validate which hypermedia links are shown or hidden based on roles.
Let’s walk through how to test conditional links using @WithMockUser and Spring HATEOAS.
Why Conditional Links Matter
In hypermedia-driven REST APIs, links such as “update”, “delete”, or “admin” should be shown only to users with the appropriate authority.
For example, only an admin might see:
"_links": {
"delete": {
"href": "/users/5"
}
}
Whereas a regular user should not see that link.
Setup: Controller with Conditional Links
Here’s a controller that adds links based on user roles:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public EntityModel<User> getUser(@PathVariable Long id, Authentication auth) {
User user = userService.findById(id);
EntityModel<User> model = EntityModel.of(user);
model.add(linkTo(methodOn(UserController.class).getUser(id)).withSelfRel());
if (auth != null && auth.getAuthorities().stream()
.anyMatch(granted -> granted.getAuthority().equals("ROLE_ADMIN"))) {
model.add(linkTo(methodOn(UserController.class).deleteUser(id)).withRel("delete"));
}
return model;
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
Writing Tests with @WithMockUser
To validate whether the correct links appear for different roles, use @WebMvcTest combined with @WithMockUser.
✅ Test: Admin User Sees the Delete Link
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
@WithMockUser(username = "admin", roles = {"ADMIN"})
void adminShouldSeeDeleteLink() throws Exception {
User user = new User(5L, "Alice");
given(userService.findById(5L)).willReturn(user);
mockMvc.perform(get("/users/5"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.delete.href").value("http://localhost/users/5"));
}
}
❌ Test: Regular User Should NOT See the Delete Link
@Test
@WithMockUser(username = "bob", roles = {"USER"})
void userShouldNotSeeDeleteLink() throws Exception {
User user = new User(5L, "Alice");
given(userService.findById(5L)).willReturn(user);
mockMvc.perform(get("/users/5"))
.andExpect(status().isOk())
.andExpect(jsonPath("$._links.delete").doesNotExist());
}
Tips and Best Practices
- Always test both inclusion and exclusion of conditional links to cover security boundaries.
- Use
@WithMockUserto quickly simulate users without needing full authentication setup. - For more complex setups, use
SecurityMockMvcRequestPostProcessors.jwt()if you’re working with OAuth2/JWT-based security. - If your logic checks for custom claims or attributes, consider using
@WithSecurityContext.
Summary
Conditional links are essential to secure, self-describing hypermedia APIs. Spring makes it easy to expose links based on roles and just as easy to test that logic using @WithMockUser. By simulating different user roles in tests, you can ensure your API behaves securely and predictably across a variety of access levels.




