jwt 사용하면서 스프링 테스트 코드 작성하는 법(@WithMockUser)

ᄋᄌᄒ·2023년 8월 13일
0
post-thumbnail

📨 시작 전에

최근 프로젝트에 박차를 가하고 있는데, 그러다보니 문제가 발생하는 부분이 굉장히 많다. 테스트 코드 또한 마찬가지다. 메인이랑은 또 다른 부분도 존재하기 때문에 유의하면서 진행해야한다.


본론

📌 프로젝트에 관하여

현재 필자가 하고 있는 프로젝트는 주로 게시글 작성 관련한 서비스를 다루고 있다. jwt를 사용중이기 때문에 개인 정보는 토큰을 통해 조회할 수 있도록 해두었다. 서비스에서도 이를 활용하여 SecurityUtil.getUsername()을 호출하여 현재 로그인 중인 member의 username이 무엇인지를 확인하여 로직을 수행하돌고 하였다.

📌 문제 발생

board 게시글에 대한 service test code를 작성 중인데, 문제가 있다. 이전에는 주로 서비스 메서드를 호출하고 매개변수에 username을 통해 Member entity 정보를 가져올 수 있도록 세팅해두었었다.

하지만 매개변수가 많아지고 service 내에서도 SecurityUtil.getUsername()을 호출하면 controller에서 활용하던 방식을 그대로 유지할 수 있기 때문에 굳이 두번 호출할 필요가 없다고 생각하여 create, edit, delete 의 경우에는 service 내에서 회원정보를 SecurityUtil.getUsername()을 통해 가져오도록 설정해두었다. 하지만 testCode를 작성할 때는 우리가 http에 정보를 담는 방법이 꽤나 까다롭다.
아니, 애초에 SecurityUtil.getUsername()에 username을 담기만 하면 되는 것인데 그 과정이 복잡한 것이다. 로그인을 하고, 로그인을 통한 token 값을 받아오고 페이지 이동까지... responseEntity를 통해 path를 왔다갔다 하기에는 우리가 설정해야할 것은 username하나뿐이라는 점이 굉장히 심기가 거슬린다.

📌 해결 방법

물론 바로 해결방법을 생각해낸 것은 아니지만 여러 방안을 생각하다가 위의 결론처럼 생각한것이다. username하나만 세팅하면 되는데.. 다른사람들도 비슷한 생각하지 않았을까라는 생각말이다.

📌 @WithMockUser

이 어노테이션을 통해 username, password, roles 등 Spring Securtiy에 설정한 member 정보를 가짜로 세팅할 수 있다. 정확히는 사용자 인증 정보를 담은 Authentication을 UsernamePasswordAuthenticationToken으로 넣어주고, Principal은 User 객체에 넣어 SecurityContext에 보관해 준다.

@WithMockUsername(username= "Member1", password = "12345678")
@Test
void test(){}

이를 사용하려면 아래와 같이 라이브러리 추가를 해주어야한다.

testImplementation 'org.springframework.security:spring-security-test' // 여기에 있는 기능

📌 @WithMockCustomUser

이 어노테이션을 사용하면 커스텀된 Authentication을 사용할 수 있다는 것이다. 그리고 중복되는 정보 세팅도 줄여줄 수 있다.

그렇다면 사용자가 직접 어노테이션을 만들어야하는데 아래와 같이 진행하면 된다.

(package com.example.project.annotation.withMockUser)

@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public @interface WithMockCustomUser {
    String username() default  "member1";

    String password() default "12345678";
}

(package com.example.project.annotation.withMockUser)

public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
    @Override
    public SecurityContext createSecurityContext(WithMockCustomUser annotation) {

        final SecurityContext securityContext = SecurityContextHolder.createEmptyContext();

        final UsernamePasswordAuthenticationToken authenticationToken
                = new UsernamePasswordAuthenticationToken
                (annotation.username(), "password");

        securityContext.setAuthentication(authenticationToken);
        return securityContext;
    }
}
  • 주의 사항
    이 어노테이션을 생성할 때는 꼭 test파일 안에다가 생성하길 바란다. 필자의 경우는 main 파일안에서 생성하려 했는데 라이브러리에서 불러올 수가 없어서 애를 먹었다.

✉️ 끝 마치고

대부분 이 어노테이션을 사용하는 것은 컨트롤러를 위함이었지만 필자 프로젝트에서는 SecurityUtil.getUsername()을 서비스에서도 사용할 수 있음을 이용하였기에 service 테스트 코드에서도 사용되었다. 과연 이 방법은 올바른 방법인 것인지 확인할 필요가 있다고 생각한다. 서칭할 때 생각보다 필자와 같은 상황에서 사용하는 경우는 많이 보지 못했기 때문에 드는 의심이다.

1개의 댓글

comment-user-thumbnail
2023년 8월 13일

정보 감사합니다.

답글 달기