최근 프로젝트에 박차를 가하고 있는데, 그러다보니 문제가 발생하는 부분이 굉장히 많다. 테스트 코드 또한 마찬가지다. 메인이랑은 또 다른 부분도 존재하기 때문에 유의하면서 진행해야한다.
현재 필자가 하고 있는 프로젝트는 주로 게시글 작성 관련한 서비스를 다루고 있다. 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하나만 세팅하면 되는데.. 다른사람들도 비슷한 생각하지 않았을까라는 생각말이다.
이 어노테이션을 통해 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' // 여기에 있는 기능
이 어노테이션을 사용하면 커스텀된 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;
}
}
대부분 이 어노테이션을 사용하는 것은 컨트롤러를 위함이었지만 필자 프로젝트에서는 SecurityUtil.getUsername()을 서비스에서도 사용할 수 있음을 이용하였기에 service 테스트 코드에서도 사용되었다. 과연 이 방법은 올바른 방법인 것인지 확인할 필요가 있다고 생각한다. 서칭할 때 생각보다 필자와 같은 상황에서 사용하는 경우는 많이 보지 못했기 때문에 드는 의심이다.
정보 감사합니다.