Search

Mock 테스트와 401 에러

Tags
Test
Date
2024/06/25

1. 개요

현재 자바 기반의 프로젝트를 코틀린으로 변환하고 있으며, 이와 동시에 기존에 작성하지 못했던 Controller 테스트를 작성하고 있습니다. 하지만 Spring Security를 설정하고 나서 계속 401 에러가 발생하고 있는데, 이를 해결하는 과정을 서술해보고자 합니다.

2. 테스트 코드

@Test @DisplayName("메뉴 카테고리 별 조회 테스트") fun getMenusTest() { val url = "/api/menus" val queryParams = LinkedMultiValueMap<String, String>() queryParams.add("restaurantName", "FOOD_COURT") whenever(menuQueryService.getMenusGroupedByCategory(Restaurant.FOOD_COURT)) .thenReturn(MenuCategoryListResponse(listOf())) val requestActions = mockMvc.perform( MockMvcRequestBuilders.get(url) .queryParams(queryParams) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer token") ) requestActions .andExpect(status().isOk) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andDo(print()) }
Kotlin
복사
테스트 코드는 다음과 같이 작성되어 있습니다.
쉽게 요약하면 /api/menus API를 호출하고 응답 코드가 200이 오는지, 반환되는 값은 JSON 형식인지 확인하는 코드입니다.
SecurityConfig.java
private val AUTH_WHITELIST = arrayOf( "/", "/api/oauths/kakao", "/api/oauths/apple", "/api/menus/**", "/api/meals/**", "/admin/login", "/api/reviews", "/api/reviews/menus/**", "/api/reviews/meals/**" ) @Bean @Throws(Exception::class) protected fun filterChain(http: HttpSecurity): SecurityFilterChain { http .csrf { c -> c.disable() } .authorizeHttpRequests { authorize -> authorize .requestMatchers(*AUTH_WHITELIST).permitAll() ...
Kotlin
복사
Spring Security 설정도 위에 보이다싶이 /api/menus 경로를 허용해주고 있습니다.
이는 곧 401 에러가 발생하면 안됨을 의미합니다.
그렇다면 왜 이런 이슈가 발생할까요?

3. 트러블 슈팅

문제는 어노테이션에 있었습니다.
@AutoConfigureWebMvc @WebMvcTest(MenuController::class) @ActiveProfiles("test") class MenuControllerTest @Autowired constructor( var mockMvc: MockMvc, val objectMapper: ObjectMapper ) {
Kotlin
복사
기존의 테스트 당시 저는 다음과 같이 테스트 어노테이션을 구성했는데요, 짧게 두 어노테이션의 기능을 살펴보고 가겠습니다.
@WebMvcTest 는 컨트롤러 테스트에 중점을 둡니다. 즉, Controller를 제외한 Service, Repository, Configuration 등은 로드 되지 않습니다.
자연스럽게 설정한 SecurityConfig 도 불러와지지 않습니다.
이를 해결하려면 다음 어노테이션을 추가해야합니다.
@AutoConfigureMockMvc @SpringBootTest
Kotlin
복사
@SpringBootTest는 전체 애플리케이션 컨텍스트를 로드하므로, 필요한 모든 빈이 로드되고 설정되어 통합 테스트가 가능합니다. 따라서, JWT 인증이나 다른 서비스 빈이 제대로 로드되어 테스트가 성공적으로 수행될 수 있습니다.
또한, @AutoConfigureMockMvc는 MockMvc 빈을 구성하고 제공하는 데 사용됩니다. 만약 실제 환경과 동일한 환경을 구축하여 테스트를 해야 한다면 @SpringBootTest를 결합하여 사용합니다.
위와 같이 구성하면 문제는 쉽게 해결됩니다.

4. 더 나아가서

하지만 테스트는 고립되어야 합니다.
그래서 모든 빈을 불러오는 @AutoConfigureMockMvc 보다 Controller 단위 테스트에 집중할 수 있는 @WebMvcTest 를 사용하여 이를 리팩토링 해봅시다.
결국 문제가 됐던 부분은 권한이 없어도 들어갈 수 있게 설정했음에도 불궇고 @WebMvcTest를 사용하면 해당 설정이 불러오지 않음으로 자동으로 권한을 체크함에 있었습니다.
 어차피 상관 없다면 임의로 권한을 부여하면 되지 않는가?
우리는 해당 물음에 대한 답변으로 @WithMockUser,@WithUserDetails 등을 사용할 수 있습니다.
변경된 테스트 코드의 전체는 다음과 같습니다.
@WebMvcTest(MenuController::class) @ActiveProfiles("test") class MenuControllerTest @Autowired constructor( var mockMvc: MockMvc, val objectMapper: ObjectMapper ) { @MockBean lateinit var menuQueryService: MenuQueryService @Test @DisplayName("메뉴 카테고리 별 조회 테스트") @WithMockUser fun getMenusTest() { val url = "/api/menus" val queryParams = LinkedMultiValueMap<String, String>() queryParams.add("restaurant", Restaurant.FOOD_COURT.name) whenever(menuQueryService.getMenusGroupedByCategory(Restaurant.FOOD_COURT)) .thenReturn(MenuCategoryListResponse(listOf())) val requestActions = mockMvc.perform( MockMvcRequestBuilders.get(url) .queryParams(queryParams) .contentType(MediaType.APPLICATION_JSON) .header("Authorization", "Bearer token") ) requestActions .andExpect(status().isOk) .andExpect(content().contentType(MediaType.APPLICATION_JSON)) .andDo(print()) } }
Kotlin
복사

5. Reference