Spring Boot version: 3.1.4 Java version: Oracle 17.0.10 OS: Ubuntu 22.04.4 LTS
Description
Rest controller
@RestController
@RequestMapping("/api/v1/homes")
@RequiredArgsConstructor
public class HomeController {
private final HomeService homeService;
private final SecurityService securityService;
@GetMapping
@PreAuthorize("""
hasAnyRole('ADMIN', 'CLIENT')
and
@securityService.isCurrentUserBelongToCompany(#companyId)
""")
public ResponseEntity<List<HomeDto>> getAllHomes(@RequestParam Long companyId) {
List<HomeDto> homes = homeService.getAllHomes(companyId);
return ResponseEntity.ok(homes);
}
}
SecurityService class
@Component
@RequiredArgsConstructor
public class SecurityService {
private final AccountRepository accountRepository;
public boolean isCurrentUserBelongToCompany(Long companyId) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UUID userId = UUID.fromString(authentication.getName());
Account account = accountRepository
.getAccountByUserId(userId);
return account.getCompany().getId().equals(companyId);
}
}
Rest controller test
@WebMvcTest(HomeController.class)
class HomeControllerTest {
private static final String ENDPOINT = "/api/v1/homes";
@Autowired
private MockMvc mockMvc;
@MockBean
private HomeService homeService;
@MockBean
private SecurityService securityService;
@Test
@WithMockUser(roles = {"ADMIN"})
void should_get_all_homes_by_company_id() throws Exception {
Long companyId = 1L;
HomeDto homeDto = new HomeDto(
1L,
"name",
"desc"
);
when(homeService.getAllHomes(companyId))
.thenReturn(List.of(homeDto));
when(securityService.isCurrentUserBelongToCompany(companyId))
.thenReturn(true);
ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders
.get(ENDPOINT)
.param("companyId", companyId.toString())
);
resultActions
.andDo(MockMvcResultHandlers.print())
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers
.jsonPath("size()")
.value(1)
).andExpect(MockMvcResultMatchers
.jsonPath("$[0].id")
.value(homeDto.id())
).andExpect(MockMvcResultMatchers
.jsonPath("$[0].name")
.value(homeDto.name())
).andExpect(MockMvcResultMatchers
.jsonPath("$[0].description")
.value(homeDto.description())
);
verify(securityService).isCurrentUserBelongToCompany(companyId);
verify(homeService).getAllHomes(companyId);
}
}
Runing test
When I run the test I get there was zero interaction with mock securityService in
verify(securityService).isCurrentUserBelongToCompany(companyId);
but when I change the annotation @WebMvcTest(HomeController.class) on HomeControllerTest class to
@SpringBootTest
@AutoConfigureMockMvc
class HomeControllerTest {
//
}
The test runs as expected
Comment From: wilkinsona
This should work as @WebMvcTest will auto-configure Spring Security. Unfortunately, it's hard to tell what that's not the case for you from a handful of code snippets as there are too many unknowns. If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.
Comment From: voidnowhere
I apologize, here is a sample that reproduces the problem. demo.zip
Comment From: wilkinsona
As described in the documentation, you need to use @Import to include your SecurityConfig in the sliced WebMvc test:
@Import(SecurityConfig.class)
@WebMvcTest(HomeController.class)
class HomeControllerTest {
You also need to name the mocked SecurityService as you referring to it by name from the @PreAuthorize expression:
@MockBean(name = "securityService")
private SecurityService securityService;
If you have any further questions, please follow up on Stack Overflow. As mentioned in the guidelines for contributing, we prefer to use GitHub issues only for bugs and enhancements.