통합테스트 ‐ Spring Security - f-lab-edu/jshop GitHub Wiki

스프링 시큐리티 테스트

Controller, Service, Repository 레이어에 대한 단위테스트를 진행하고 스프링 시큐리티에 대한 단위테스트또한 하고싶어졌다.

처음엔 스프링 시큐리티의 필터 하나하나 단위테스트 해보려 했다. 하지만 필터에 주입해줘야할 객체들이 많고 복잡해(특히 AuthenticationManager) 통합테스트로 진행하기로 결정했다.

통합테스트

처음엔 @SpringBootTest 로 통합 테스트로 진행했다.

하지만 이렇게 통합 테스트를 진행하니 쓸데없는 Controller, Repository 등과 JPA 등이 동작했다.

이경우 스프링 시큐리티에 대한 독립적인 테스트가 아니라고 생각해 스프링 시큐리티만 통합테스트 하는 방법을 찾아봤다.

스크린샷 2024-06-10 15 21 06

hibernate, SQL 로그가 마구 찍히는것을 볼 수 있다.

스프링 시큐리티만 테스트

스프링 시큐리티만 통합테스트할 수 있는 방법에 대해 생각해보다, @SpringBootTestclasses 필드를 사용해 SecurityConfig 만 사용하면 테스트가 가능할것 같아 시도해봤다. 그외, JwtUtil 와 같은 유틸 클래스도 추가해 실행해줬다.

하지만 실행하니 다음과 같은 오류와 함께 실행되지 않았다.

Description:

Parameter 0 of method setFilterChains in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean named 'A Bean named mvcHandlerMappingIntrospector of type org.springframework.web.servlet.handler.HandlerMappingIntrospector is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.' that could not be found.


Action:

Consider defining a bean named 'A Bean named mvcHandlerMappingIntrospector of type org.springframework.web.servlet.handler.HandlerMappingIntrospector is required to use MvcRequestMatcher. Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.' in your configuration.

WebSecurityConfiguration 을 위해선 HandlerMappingIntrospector 이 필요하지만, 이게 없어서 생기는 문제고, Spring Security 와 Sprinv MVC가 같은 컨텍스트에 있어야 한다는 메시지가 나왔다.

Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext.' that could not be found.

혹시나 해서 @EnableWebMvc 를 사용해줬더니 문제없이 실행할 수 있었다.

예제 코드

@EnableWebMvc
@SpringBootTest(classes = {SecurityConfig.class, JwtUtil.class, ObjectMapper.class})
@AutoConfigureMockMvc
public class LoginFilterTest {

    @Autowired
    private MockMvc mockMvc;

    @MockBean
    private UserDetailsService userDetailsService;

    @Test
    @WithMockUser
    public void 로그인테스트() throws Exception {

        BCryptPasswordEncoder bpe = new BCryptPasswordEncoder();
        User u = User.builder()
            .username("user")
            .email("user")
            .password(bpe.encode("password"))
            .role("ROLE_USER")
            .build();

        UserDetails userDetails = new CustomUserDetails(u);
        when(userDetailsService.loadUserByUsername("user"))
            .thenReturn(userDetails);

        JSONObject requestBody = new JSONObject();
        requestBody.put("username", "user");
        requestBody.put("password", "password");

        ResultActions perform = mockMvc.perform(MockMvcRequestBuilders.post("/api/login")
            .contentType(MediaType.APPLICATION_JSON)
            .content(requestBody.toString()));

        perform.andExpect(status().isOk());
    }
}

내가 테스트하고자 했던 필터는 UsernamePasswordAuthenticationFilter 였고, 이 필터의 경우 UserDetailsService, UserDetails 를 사용했다.

하지만 UserDetailsService 의 경우 Repository 를 통해 DB에서 유저 데이터를 가져와야 하는 서비스다.

이경우 필터에 대한 테스트 독립성이 깨지기 때문에 UserDetailsServiceMockBean 으로 만들어 주입했다.

덕분에 DB에 다녀올 필요 없이 시큐리티 필터 레벨에서 테스트가 가능했다.