본문 바로가기

Framework/Spring

[Spring] 단위 테스트에서 @WithUserDetails 사용하기: 발생한 에러와 해결 방법 - 컴도리돌이

728x90

 

 

 

작심삼주 오블완 챌린지

오늘 블로그 완료! 21일 동안 매일 블로그에 글 쓰고 글력을 키워보세요.

www.tistory.com


단위 테스트를 작성할 때, 로그인 인증이 필요한 API 요청을 테스트하는 과정에서 @WithUserDetails 어노테이션을 사용해 인증된 상태를 시뮬레이션하려고 했습니다. 처음에는 예상보다 많은 오류가 발생해서 이 문제들을 해결하기 위해 어떻게 접근했는지 기록해 보려고 합니다. 😅

 

 

[Spring] 로그인 필요한 API 단위 테스트 - 컴도리돌이

작심삼주 오블완 챌린지오늘 블로그 완료! 21일 동안 매일 블로그에 글 쓰고 글력을 키워보세요.www.tistory.com이제는 숨을 쉬듯이 API 요청 로직을 만들지만, 단 한 번도 테스트 코드로 API 요청을 테

comdolidol-i.tistory.com

 

WithUserDetail

@WithUserDetails 어노테이션은 실제 사용자 정보를 데이터베이스에서 로드하여 테스트에서 인증된 상태를 시뮬레이션할 수 있도록 돕습니다. 이 어노테이션은 @WithMockUser와 달리 가짜 사용자 데이터가 아니라 실제 데이터베이스에 저장된 사용자 정보를 사용하기 때문에 실제 환경에 가까운 테스트를 할 수 있습니다.

 

예를 들어, @WithUserDetails("realUser")라고 설정하면, "realUser"라는 이름의 사용자를 UserDetailsService에서 로드하여 실제 인증 과정을 수행하게 됩니다. 이를 통해 데이터베이스에 존재하는 사용자 정보를 활용하여, 실제 환경과 유사한 조건에서 API 엔드포인트를 테스트할 수 있습니다.

import org.springframework.security.test.context.support.WithUserDetails;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest
public class ControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithUserDetails("realUser")
    public void testAuthenticatedEndpointWithRealUser() throws Exception {
        mockMvc.perform(get("/api/protected-endpoint"))
               .andExpect(status().isOk())
               .andDo(print());
    }
}

 

 

위와 같이 @WithUserDetails를 설정하면 인증된 사용자의 정보가 로드되며, 보호된 엔드포인트에 대한 요청을 정상적으로 처리할 수 있습니다. 하지만 이 과정에서 예상치 못한 에러가 발생할 수 있습니다. 😓

No qualifying bean of type 'org.springframework.security.core.userdetails.UserDetailsService' available: expected single matching bean but found 2: customUserDetailService, userDetailsService

 

이 에러는 UserDetailsService 빈이 두 개 이상 등록되어 있을 때 발생합니다. 제가 customUserDetailService를 커스텀으로 구현하여 사용했는데, 스프링이 어느 빈을 사용해야 할지 명확히 알지 못해 두 빈을 모두 찾아낸 것입니다. 🧐

 

이 문제를 해결하려면, @WithUserDetails 어노테이션에서 사용할 UserDetailsService 빈을 명시적으로 지정해야 합니다. userDetailsServiceBeanName 속성에 사용하고자 하는 UserDetailsService 빈의 이름을 지정함으로써 해결할 수 있습니다.

import org.springframework.security.test.context.support.WithUserDetails;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest
public class ControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithUserDetails(value = "realUser", userDetailsServiceBeanName = "customUserDetailService")
    public void testAuthenticatedEndpointWithRealUser() throws Exception {
        mockMvc.perform(get("/api/protected-endpoint"))
               .andExpect(status().isOk())
               .andDo(print());
    }
}

 

하지만, 여전히 다른 문제로 이어졌습니다. 😓

java.lang.IllegalStateException: Unable to create SecurityContext using @org.springframework.security.test.context.support.WithUserDetails(setupBefore=TEST_METHOD, userDetailsServiceBeanName="customUserDetailService", value="realUseruser")

 

이 에러는 "realUseruser"와 같은 잘못된 사용자 이름이 value 속성에 전달되었기 때문입니다. 😅 이는 문자열에서 사용자 이름을 잘못 설정했을 가능성도 있지만, 더 중요한 원인은 @WithUserDetails 어노테이션이 실행되는 타이밍과 관련이 있습니다.

 

@WithUserDetails 어노테이션의 setupBefore 속성으로 실행 타이밍을 설정할 수 있습니다. 기본적으로 setupBefore 값은 TestExecutionEvent.TEST_METHOD입니다. 이 값을 TestExecutionEvent.TEST_EXECUTION으로 변경하면, 테스트 메소드가 실행되기 전에 인증 정보를 설정할 수 있어 잘못된 상태로 테스트가 실행되는 문제를 방지할 수 있습니다.

import org.springframework.security.test.context.support.WithUserDetails;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.MockMvc;

@WebMvcTest
public class ControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    @WithUserDetails(value = "realUser", 
    		userDetailsServiceBeanName = "customUserDetailService",
            	setupBefore = TestExecutionEvent.TEST_EXECUTION)
    public void testAuthenticatedEndpointWithRealUser() throws Exception {
        mockMvc.perform(get("/api/protected-endpoint"))
               .andExpect(status().isOk())
               .andDo(print());
    }
}

 

이렇게 @WithUserDetails를 사용하여 인증된 상태에서 테스트를 수행하는 과정에서 발생할 수 있는 여러 문제와 해결 방법을 살펴보았습니다. 가장 중요한 점은 UserDetailsService 빈을 명확히 지정하는 것테스트 실행 타이밍을 조절하는 것입니다. 이를 통해 인증 관련 문제를 해결하고, 실제 환경과 유사한 조건에서 API 테스트를 안전하고 효율적으로 진행할 수 있습니다.

 

단위 테스트는 항상 프로젝트의 품질을 유지하는 데 중요한 역할을 합니다. 로그인 인증을 필요로 하는 API 테스트에서 발생할 수 있는 다양한 오류를 해결하는 과정은, 결국 더 좋은 품질의 코드와 테스트 환경을 구축하는 데 중요한 경험이 될 것입니다. 이 글에서 다룬 내용을 바탕으로 여러분도 더 효율적인 인증 테스트 환경을 구성할 수 있기를 바랍니다! 🙌