Interview question
Test Spring resource ownership with MockMvc
Tests anonymous, owner, non-owner, and missing-resource outcomes through the real Spring Security filter chain.
TL;DR
Tests anonymous, owner, non-owner, and missing-resource outcomes through the real Spring Security filter chain.
MockMvc, Spring Security test support, authentication versus authorization, object ownership, 401/403/404 behavior, and boundary tests.
Practice the problem like a real interview: restate, reason, implement, and test.
Test GET /orders/{id}. Anonymous callers receive 401, the owner receives 200, an authenticated non-owner receives 403, and a missing order receives 404. The test must pass through the Spring Security filter chain and execute real ownership logic rather than mocking the authorization decision itself.
anonymous GET /orders/10 -> 401
alice owns order 10 -> alice gets 200
bob requests order 10 -> 403
alice requests order 99 -> 404
@WebMvcTest and MockMvc with Spring Security enabled.@org.springframework.stereotype.Service
class OrderQuery {
private final OrderRepository orders;
OrderQuery(OrderRepository orders) { this.orders = orders; }
OrderView get(long id, org.springframework.security.core.Authentication caller) {
var order = orders.findById(id).orElseThrow(OrderNotFound::new);
if (!order.ownerUsername().equals(caller.getName())) {
throw new org.springframework.security.access.AccessDeniedException("forbidden");
}
return new OrderView(order.id(), order.total());
}
}
@org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest(OrderController.class)
@org.springframework.context.annotation.Import({SecurityConfig.class, OrderQuery.class})
class OrderOwnershipTest {
@org.springframework.beans.factory.annotation.Autowired
org.springframework.test.web.servlet.MockMvc mvc;
@org.springframework.test.context.bean.override.mockito.MockitoBean
OrderRepository orders;
@org.junit.jupiter.api.Test void anonymous_is_unauthorized() throws Exception {
mvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get("/orders/10"))
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.status().isUnauthorized());
}
@org.junit.jupiter.api.Test
@org.springframework.security.test.context.support.WithMockUser(username = "alice")
void owner_can_read() throws Exception {
org.mockito.Mockito.when(orders.findById(10L))
.thenReturn(java.util.Optional.of(new OrderRow(10, "alice", "12.00")));
mvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get("/orders/10"))
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.status().isOk())
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath("$.id").value(10));
}
@org.junit.jupiter.api.Test
@org.springframework.security.test.context.support.WithMockUser(username = "bob")
void other_user_is_forbidden() throws Exception {
org.mockito.Mockito.when(orders.findById(10L))
.thenReturn(java.util.Optional.of(new OrderRow(10, "alice", "12.00")));
mvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get("/orders/10"))
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.status().isForbidden());
}
@org.junit.jupiter.api.Test
@org.springframework.security.test.context.support.WithMockUser(username = "alice")
void missing_order_is_not_found() throws Exception {
org.mockito.Mockito.when(orders.findById(99L))
.thenReturn(java.util.Optional.empty());
mvc.perform(org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get("/orders/99"))
.andExpect(org.springframework.test.web.servlet.result.MockMvcResultMatchers.status().isNotFound());
}
}
Each request performs one indexed resource lookup and a constant-time ownership comparison. The test suite is constant-sized but deliberately covers each security outcome.
Authentication, not a request parameter.Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Answer a similar question in a different context.