인증된 사용자가 접근할 수 있는 기능 테스트하기
- 실제 DB에 저장되어 있는 정보에 대응하는 인증된 Authentication에 필요하다.
- @WithMockUser로는 처리할 수 없다.(버그)
인증된 사용자를 제공할 커스텀 애노테이션 만들기
- @WithAccount
커스텀 애노테이션 생성
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithAccountSecurityContextFactory.class)
public @interface WithAccount {
String value();
}
SecurityContextFactory 구현
package me.weekbelt.studyolle;
@RequiredArgsConstructor
public class WithAccountSecurityContextFactory implements WithSecurityContextFactory<WithAccount> {
private final AccountService accountService;
// @WithUserDetails(value = "joohyuk", setupBefore = TestExecutionEvent.TEST_EXECUTION)가 원래 하는일
@Override
public SecurityContext createSecurityContext(WithAccount withAccount) {
String nickname = withAccount.value();
// 회원 가입
SignUpForm signUpForm = new SignUpForm();
signUpForm.setNickname(nickname);
signUpForm.setEmail(nickname + "@hanmail.net");
signUpForm.setPassword("12345678");
accountService.processNewAccount(signUpForm);
// Authentication 만들고 SecurityContext에 넣어주기
UserDetails principal = accountService.loadUserByUsername(nickname);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities());
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
테스트 코드 작성
package me.weekbelt.studyolle.settings;
@SpringBootTest
@AutoConfigureMockMvc
class SettingsControllerTest {
@Autowired
MockMvc mockMvc;
@Autowired
AccountRepository accountRepository;
@AfterEach
public void afterEach() {
accountRepository.deleteAll();;
}
@WithAccount("joohyuk")
@DisplayName("프로필 수정 폼")
@Test
public void updateProfileForm() throws Exception {
String bio = "짧은 소개를 수정하는 경우";
mockMvc.perform(get("/settings/profile"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("account"))
.andExpect(model().attributeExists("profile"));
}
@WithAccount("joohyuk")
@DisplayName("프로필 수정 하기 - 입력값 정상")
@Test
public void updateProfile() throws Exception {
String bio = "짧은 소개를 수정하는 경우";
mockMvc.perform(post("/settings/profile")
.param("bio", bio)
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/settings/profile"))
.andExpect(flash().attributeExists("message"))
;
Account joohyuk = accountRepository.findByNickname("joohyuk");
assertThat(joohyuk.getBio()).isEqualTo(bio);
}
@WithAccount("joohyuk")
@DisplayName("프로필 수정 하기 - 입력값 에러")
@Test
public void updateProfile_error() throws Exception {
String bio = "길게 소개를 수정하는 경우, 길게 소개를 수정하는 경우, 길게 소개를 수정하는 경우, 길게 소개를 수정하는 경우";
mockMvc.perform(post("/settings/profile")
.param("bio", bio)
.with(csrf()))
.andExpect(status().isOk())
.andExpect(view().name("settings/profile"))
.andExpect(model().attributeExists("account"))
.andExpect(model().attributeExists("profile"))
.andExpect(model().hasErrors())
;
Account joohyuk = accountRepository.findByNickname("joohyuk");
assertThat(joohyuk.getBio()).isNull();
}
}
프로필 수정 테스트를 하기 위해서 기본적으로 계정에 대한 정보가 있어야 한다.
그래서 테스트 클래스에 @WithMockUser를 주면 Authentication안에 UsernamePasswordAuthenticationToken타입으로 username을 "user", password를 "password"를 넣어주는데 쓸 수 없다. 우리는 진짜 DB에 저장된 데이터가 SecurityContext에 들어 있어야 한다.
@WithUserDetails("username")을 쓸 수 있다. 우리가 만든 UserDetailsService를 사용해서 username에 해당하는 데이터를 가지고 테스트를 실행하게 된다. 그래서 테스트 시작 전에 @BeforeEach를 이용해 계정 정보를 저장하고 실행하면 테스트가 작동할 것 같지만 하지 않는다. 왜냐하면 @BeforeEach가 실행하기 전에 @WithUSerDetails("username")이 먼저 실행하기 때문이다. 그래서 @BeforeEach가 실행 후와 테스트 코드 직전에 실행하기 위해
@WithUserDetails(value = "joohyuk", setupBefore = TestExecutionEvent.TEST_EXECUTION)
이렇게 사용하게 되는데 버그가 있다. 따라서 위처럼 Custom한 Annotation을 작성하고 SecurityContextFactory를 따로 구현하여 계정 정보를 활성화한다.
참조: https://docs.spring.io/spring-security/site/docs/current/reference/html5/
참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#
'스프링과 JPA 기반 웹 어플리케이션 개발 > 1부 (개발환경, 회원가입, 로그인, 계정설정)' 카테고리의 다른 글
26. 패스워드 수정 (0) | 2020.04.21 |
---|---|
25. 프로필 이미지 변경 (0) | 2020.04.21 |
23. 프로필 수정 처리 (0) | 2020.04.21 |
22. 프로필 수정 (0) | 2020.04.21 |
21. Open EntityManager (또는 Session) In View 필터 (0) | 2020.04.21 |