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.