이전에 해왔던 test와 약간 다르다. 이전은 회원 가입 위주였기 때문에 인증이 되지 않은 사용자에게 하는 것이였고, 지금은 인증된 사용자에 한에서 프로필 정보를 수정하는 것이다.
SettingsController.java
static final String SETTINGS_PROFILE_VIEW_NAME = "settings/profile";
static final String SETTINGS_PROFILE_URL = "/settings/profile";
static은 알다시피 클래스 변수이다. 그러므로 static final은 객체(인스턴스)가 아닌 클래스에 존재하는 단 하나의 상수이다. 즉 객체마다 값이 바뀌는 것이 아닌 클래스에 존재하는 상수이므로 선언과 동시에 초기화를 해 주어야하는 클래스 상수이다.
요청을 보낼때 어떤 유저가 보내는지 설정을 해야 하는데 그걸 어떻게 해야 할까?
@WithAccount("keesun")
@DisplayName("프로필 수정하기 - 입력값 정상")
@Test
void updateProfile() throws Exception {
String bio = "짧은 소개를 수정하는 경우!";
mockMvc.perform(post(SettingsController.SETTINGS_PROFILE_URL)
.param("bio", bio)
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl(SettingsController.SETTINGS_PROFILE_URL))
.andExpect(flash().attributeExists("message"));
Account keesun = accountRepository.findByNickname("keesun");
assertEquals(bio, keesun.getBio());
}
@Test
@WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void getMessageWithMockUserCustomAuthorities() {
String message = messageService.getMessage();
...
}
보통 위의 방법을 사용한다. 하지만 현재 우리는 이것을 사용할 수 없다. 왜 ? 위 어플리케이션에는 진짜 데이터 베이스에 들어있는 데이터, 그 유저가 컨텍스트에 들어있어야 한다.
@BeforeEach
void beforEach() {
SignUpForm signUpForm = new SignUpForm();
signUpForm.setNickname("yuri");
signUpForm.setEmail("yuri@naver.com");
signUpForm.set
왠지 @BeforeEach 를 사용하면 될 것 같은데 .. 안된다. 이 어노테이션 전에 이미 @WithAccount("keesun") 가 실행된다. 스프링 시큐리티 중에 실행하는 위치를 설정할 수도 있다.
@WithAccount(value = "keesun", setupBefore = TestExecutionEvent.TEST_EXECUTION)
Before 다음, test code 바로 직전에 실행하라는 뜻인데 버그가 있다. 이게 제대로 동작하지 않는다. 데이터를 넣기 전에 기존에 해당하는 유저 정보를 가져오다가 실패한다. UserDetails에는 keesun 이 없기 때문에 에러가 발생한다. 스프링 시큐리티 테스트 기능이 junit 제대로 동작하지 않는다. 나중에 고쳐지기를 바라며 ... 그래서 다른 것을 사용할 것이다.
따라서 @WithSecurityContext를 활용하여 확장을 할 수 있는 기능을 사용하자.
WithAccount.java 라는 커스텀한 애노테이션를 만든다. SecurityContext에다가 factory 애트리뷰트를 사용해서 SecurityContext를 만들어줄 factory를 만들 것이다.
@interface [어노테이션 명]이라는 형태로 어노테이션을 만들면 된다. 어노테이션은 멤버를 가질 수 있으며 타입과 이름, 디폴트값을 설정할 수 있다. 디폴트값을 따로 지정해주지 않으면 기본 엘리멘트가 된다.
※ 엘리멘트 뒤에는 ( ) 괄호를 붙여야 한다.
WithAccountSecurityContextFactory.java 생성한다. 이것은 WithSecurityContextFactory를 확장해서 만든다. WithSecurityContextFactory를 구현해야 한다.
완벽한 추상화를 달성하기 위함. 인터페이스를 사용함으로써, 다중상속의 기능을 지원할 수 있다.
WithSecurityContextFactory 타입에 방금 만든 WithAccount 를 넣어준다.
@RequiredArgsConstructor
public class WithAccountSecurityContextFactory implements WithSecurityContextFactory<WithAccount> {
private final AccountService accountService;
@Override
public SecurityContext createSecurityContext(WithAccount withAccount) {
String nickname = withAccount.value();
SignUpForm signUpForm = new SignUpForm();
signUpForm.setNickname(nickname);
signUpForm.setEmail(nickname + "@email.com");
signUpForm.setPassword("12345678");
accountService.processNewAccount(signUpForm);
UserDetails principal = accountService.loadUserByUsername(nickname);
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
그럼 이런 메소드를 등록해야 한다. 이 클래스는 빈으로 등록되어있기 때문에 필요한 것들을 주입받을 수 있다. AccountService 을 주입 받아서 사용하자.
원래는 시큐리티 컨텍스트만 만들어서 리턴하면 된다.
value()를 해서 닉네임을 받아오자. 받아온 다음에 해당되는 데이터를 읽어서 시큐리티 컨텍스트에 넣어준다.
WithAccount를 사용할 때마다 그 account에 해당하는 계정을 만들기 때문에 매번 테스트를 사용한 다음에는 지워줘야 한다.
SettubgsController.java
@AfterEach
void afterEach() {
accountRepository.deleteAll();
}
그럼 withAccount를 여러번 사용해도 문제가 없다.
이후 테스트를 실행하면 기선이라는 account를 만들고 기선이라는 account를 스프링 시큐리티 컨텍스트에 넣은 다음에 테스트를 실행하게 된다. 따라서 성공하게 됨.
@WithAccount("keesun") 어노테이션은 상당히 편리하다.
@Retention: 자바 컴파일러가 어노테이션을 다루는 방법을 기술하며, 특정 시점까지 영향을 미치는지를 결정한다.
출처 : 인프런 백기선님의 스프링과 JPA 기반 웹 애플리케이션 개발
https://elfinlas.github.io/2017/12/14/java-annotation/
https://m.blog.naver.com/PostView.nhn?blogId=goddlaek&logNo=220889229659&proxyReferer=https:%2F%2Fwww.google.com%2F
https://joochang.tistory.com/76
https://coding-factory.tistory.com/575