inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

스프링부트 테스트에서 @AuthenticationPrincipal UserDetails userDetails 테스트

1149

유요한

작성한 질문수 49

0

컨트롤러 테스트에서 @AuthenticationPrincipal UserDetails userDetails에 해당하는 부분을 테스트하려고 하는데 계속 실패해서 질문드립니다 ㅠㅠ

 

    // 회원 탈퇴
    @DeleteMapping("/{memberId}")
    @Tag(name = "member")
    @Operation(summary = "삭제 API", description = "유저를 삭제하는 API입니다.")
    @PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
    public String remove(@PathVariable Long memberId,
                         @AuthenticationPrincipal UserDetails userDetails) {
        try {
            String email = userDetails.getUsername();
            log.info("email : " + email);
            String remove = memberService.removeUser(memberId, email);
            return remove;
        } catch (Exception e) {
            return "회원탈퇴 실패했습니다. :" + e.getMessage();
        }
    }

이런식으로 프론트가 헤더에 accessToken을 보내주면 검증을 한 후 @AuthenticationPrincipal UserDetails userDetails이거로 정보를 가져와서 권한과 해당 유저인지 체크 후 처리하는 로직을 구성하고 있는데 이거를 테스트할 때 막혔습니다.

일단 구글에서 검색해서 나온 방법들을 다 사용해도 실패가 뜨네요.

↓구글에서 나온 방법 적용

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithAuthUserSecurityContextFactory.class)
public @interface WithAuthUser {
    String user() default "test@test.com";
    String role() default "USER";
}
public class WithAuthUserSecurityContextFactory implements WithSecurityContextFactory<WithAuthUser> {

    @Override
    public SecurityContext createSecurityContext(WithAuthUser annotation) {
        String email = annotation.user();
        String role = annotation.role();

        MemberEntity member = MemberEntity.builder()
                .memberId(1L)
                .email(email)
                .memberPw("dudtjq8990!")
                .memberName("테스터")
                .memberRole(Role.valueOf(role))
                .nickName("테스터")
                .memberPoint(0)
                .provider(null)
                .providerId(null)
                .address(AddressEntity.builder()
                        .memberAddr("서울시 강남구")
                        .memberZipCode("103-332")
                        .memberAddrDetail("102")
                        .build())
                .build();

        UserDetails userDetails = new PrincipalDetails(member);

        Authentication authentication =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        context.setAuthentication(authentication);
        return context;
    }
}
    @Test
    @WithAuthUser
    void remove() throws Exception {
        Long id = 1L;
        String email = "test@test.com";

        mockMvc.perform(delete("/api/v1/users/{memberId}", id)
                        .contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());

        verify(memberService).removeUser(id, email);
    }

 

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: No primary or single unique constructor found for interface org.springframework.security.core.userdetails.UserDetails

 

이런 오류가 발생합니다. 찾아보니까 UserDetails를 구현한 클래스 PrincipalDetails에 생성자가 없어서 나오는 오류라고 하는데 문제는 해당 클래스에 기본 생성자가 있다는 것입니다. 생성자가 있는데 계속 없다고 에러가 발생합니다. @ㅇNoArgsConstructorㅇ

스프링부트 junit5 테스트 controller

답변 1

0

인프런 AI 인턴

안녕하세요, 인프런 AI 인턴입니다.

UserDetails 관련한 PrincipalDetails 클래스에서 기본 생성자가 있다고 하셨지만, 스프링 시큐리티의 테스트에서 UserDetails 객체를 사용하기 위해서는 UserDetails를 생성하여 Authentication 객체에 설정하고 이를 SecurityContext에 주입하는 과정이 필요합니다.

@WithMockUser 어노테이션을 사용하여 UserDetails 객체를 테스트 내에서 간단하게 모의할 수 있습니다. @WithMockUser 어노테이션은 스프링 시큐리티 테스트 컨텍스트에 모의 사용자를 추가해주며, 이 사용자는 미리 정의된 권한을 가지고 있는 것으로 인식됩니다. 위에서 제공된 정보에 따르면 @WithMockUser 예시 사용법은 다음과 같습니다:

@Test
@WithMockUser(roles = "ADMIN")
void requestProtectedUrlWithUser() throws Exception {
    this.mvc.perform(get("/"));
}

위 코드는 ADMIN 역할을 가진 가짜 사용자를 이용하여 URL을 요청하는 테스트를 실행하게 됩니다. 이를 바탕으로, 여러분의 코드에서 @AuthenticationPrincipal로 주입받은 UserDetails 객체를 테스트하려면, 테스트 메서드에서 @WithMockUser를 사용하여 알맞은 권한과 함께 테스트를 진행할 수 있습니다. 권한을 “ROLE_USER” 또는 “ROLE_ADMIN”으로 설정하여 테스트를 진행해 보십시오.

예를 들어 다음과 같이 테스트를 수정할 수 있습니다:

@Test
@WithMockUser(username = "test@test.com", roles = "USER") // 혹은 "ADMIN" 권한으로 테스트
void remove() throws Exception {
    Long id = 1L;

    mockMvc.perform(delete("/api/v1/users/{memberId}", id)
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk());

    verify(memberService).removeUser(id, "test@test.com");
}

@WithMockUser로 인해 현재의 Authentication은 모의 UserDetails 객체로 채워질 것이며, 이 객체는 요청 처리 동안 사용되어 여러분이 구현한 로직을 테스트할 수 있습니다. 만약 PrincipalDetails 클래스에 특별한 구성 요소가 없다면, 테스트 작성에 있어서 @WithMockUser를 사용하는 것으로 충분합니다.

추가적으로 PrincipalDetails 클래스에 대한 문제가 여전히 발생한다면, PrincipalDetails 클래스의 생성자가 올바르게 정의되어 있고, 해당 클래스가 UserDetails 인터페이스를 올바르게 구현하고 있는지 확인해야 합니다. 생성자의 문제가 실제로 해결되었는지 다시 한번 검토해 주세요.

참고하실 수 있도록 해당 내용에 관련된 문서의 링크를 첨부합니다.

질문에 대한 답변이 되길 바랍니다. 추가적인 질문이 있으시면 언제든지 문의해 주세요.

Singleton 관련 질문입니다.

1

25

2

private 상수 테스트 관련 질문

0

82

1

void는 어떻게 테스트하나요..? void로 애초에 코딩하면 안되나요??

0

124

2

테스트 코드

1

131

2

서비스의 테스트에 관하여...

2

180

1

테스트 문서화 질문입니다

0

104

2

단위테스트 질문이 있습니다

0

94

2

컨트롤러는 모킹을 한 이유가 궁금합니다.

0

100

2

ERD 가장자리에 있는 도메인 테스트 질문

0

86

2

DTO 검증 필드에 대한 테스트 코드 작성은 어디까지?

0

131

2

OrderCreateRequest DTO에 대해서 궁금한점

0

101

2

고전파의 테스트 대역 사용 대상, 공유 의존성

0

154

2

계층 관련 질문이 있습니다.

0

137

3

'코틀린'에서는 빌더를 따로 쓰지 않는데, 이 때는 어떻게 test fixture를 만드시는지 궁금합니다

1

122

2

혹시 update 로직은 어떻게 테스트하나요? (@Setter?)

0

133

2

단위테스트와 통합테스트의 경계가 궁금합니다.

0

226

2

Service+Repository 통합테스트 관련 질문입니다.

0

149

2

OrderControllerDocsTest 작성 해봤는데요. 날짜 형식이 이상하게 나와요

0

183

2

test 용 .yml

0

89

2

throws Exception

0

78

2

카페키오스크 클래스 문의 ,,

0

87

2

Rest docs 문서용 테스트코드를 따로 작성해야 되나요?

0

171

2

테스트 코드에서 필요한 생성자

0

136

1

tearDown 순서

0

114

2