일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 제네릭 와일드 카드
- Switch Expressions
- 항해99
- raw 타입
- 함수형 인터페이스
- 바운디드 타입
- yield
- auto.create.topics.enable
- 익명 클래스
- 자바스터디
- 프리미티브 타입
- throwable
- System.out
- System.err
- junit 5
- 람다식
- 상속
- System.in
- 브릿지 메소드
- docker
- annotation processor
- 스파르타코딩클럽
- github api
- Study Halle
- 합병 정렬
- 자바할래
- 접근지시자
- 로컬 클래스
- 정렬
- 제네릭 타입
- Today
- Total
코딩하는 털보
11 to 9, Day 5 본문
Today, ToDoList
Toy Project - NGMA
- 테스트 코드 작성
- 컨트롤러 리팩토링
테스트 코드 작성
@SpringBootTest
@AutoConfigureMockMvc
@Transactional
class AccountControllerTest {
@Autowired
private MockMvc mvc;
@Autowired
private AccountService accountService;
@Autowired
private ObjectMapper objectMapper;
@BeforeEach
private void setUp() {
AccountDto account1 = new AccountDto();
account1.setEmail("jilee@example.com");
account1.setPassword("jilee123");
accountService.registerAccount(account1);
AccountDto account2 = new AccountDto();
account2.setEmail("sjlee@example.com");
account2.setPassword("sjlee123");
accountService.registerAccount(account2);
}
@Test
public void registerAccount() throws Exception {
AccountDto accountDto = new AccountDto();
accountDto.setEmail("nobody@example.com");
accountDto.setPassword("newAccount");
String accountDtoJson = objectMapper.writeValueAsString(accountDto);
mvc.perform(post("/account")
.contentType(MediaType.APPLICATION_JSON)
.content(accountDtoJson))
.andDo(print())
.andExpect(status().is3xxRedirection());
assertThat(accountService.getUserByEmail("nobody@example.com")).isNotEmpty();
}
@Test
public void registerAccountWithShortPassword() throws Exception {
AccountDto accountDto = new AccountDto();
accountDto.setEmail("nobody@example.com");
accountDto.setPassword("short");
String accountDtoJson = objectMapper.writeValueAsString(accountDto);
mvc.perform(post("/account")
.contentType(MediaType.APPLICATION_JSON)
.content(accountDtoJson))
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(content().string("can't create account, because of password."))
.andExpect(result -> assertTrue(result.getResolvedException() instanceof InvalidPasswordException));
assertThat(accountService.getUserByEmail("nobody@example.com")).isEmpty();
}
@Test
public void registerAccountWithoutPassword() throws Exception {
AccountDto accountDto = new AccountDto();
accountDto.setEmail("nobody@example.com");
String accountDtoJson = objectMapper.writeValueAsString(accountDto);
mvc.perform(post("/account")
.contentType(MediaType.APPLICATION_JSON)
.content(accountDtoJson))
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(content().string("can't create account, because of password."))
.andExpect(result -> assertTrue(result.getResolvedException() instanceof InvalidPasswordException));
assertThat(accountService.getUserByEmail("nobody@example.com")).isEmpty();
}
@Test
public void registerAccountExistEmail() throws Exception {
AccountDto accountDto = new AccountDto();
accountDto.setEmail("jilee@example.com");
accountDto.setPassword("password");
String accountDtoJson = objectMapper.writeValueAsString(accountDto);
mvc.perform(post("/account")
.contentType(MediaType.APPLICATION_JSON)
.content(accountDtoJson))
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(content().string("can't create account, because of used email."))
.andExpect(result -> assertTrue(result.getResolvedException() instanceof UsedEmailException));
assertThat(accountService.getUserByEmail("jilee@example.com")).isNotEmpty();
}
@Test
@WithUserDetails(value = "jilee@example.com", setupBefore = TestExecutionEvent.TEST_EXECUTION)
public void modifyAccount() throws Exception {
AccountDto accountDto = new AccountDto();
accountDto.setName("jilee");
accountDto.setPassword("jilee321");
String accountDtoJson = objectMapper.writeValueAsString(accountDto);
mvc.perform(post("/account/update")
.contentType(MediaType.APPLICATION_JSON)
.content(accountDtoJson))
.andDo(print())
.andExpect(status().is3xxRedirection());
}
}
@Transactional
@Transactional을 붙였다. Spring test에서 @Transactional을 붙이면 자동으로 트랜젝션을 롤백해줬었던걸 까먹고 @AfterEach로 데이터 처리하다가 갑자기 기억나서 @AfterEach는 지우고 @Transactional로 자동으로 롤백 되도록 바꾸었다.
@BeforeEach
@BeforeAll을 쓰다가 @BeforeEach 로 바꾸었다. @BeforeAll 이랑 @TestInstance(Lifecycle.PER_CLASS) 를 같이 사용하고 있었는데, (PER_CLASS가 아니면 BeforeAll 메소드가 static 메소드여야 한다...) 이렇게 테스트 하자니 테스트 메소드끼리 서로 영향을 주는 문제가 있어서 왠지 좋은 테스트 코드가 아니다 싶어서 @BeforeEach로 바꾸었다.
@WithUserDetails(value = "jilee@example.com", setupBefore = TestExecutionEvent.TEST_EXECUTION)
오늘의 하이라이트. 요놈 때문에 몇시간 고생한지 모르겠다.
이 프로젝트는 UserAccount 라는 User implements UserDetails
와 내가 계정 Entity로 사용하는 Account
의 가운데 연결 역할을 하는 클래스와 커스텀 UserDetailsService 를 사용하고 있다. 이유는 email 주소를 아이디로 사용하려고!
public class UserAccount extends User {
private Account account;
public Account getAccount() {
return account;
}
public UserAccount(Account account) {
super(account.getEmail(),
account.getPassword(),
List.of(new SimpleGrantedAuthority("ROLE_"+account.getRole())));
this.account = account;
}
public Long getAccountId() {
return account.getId();
}
}
@Service
public class AccountService implements UserDetailsService {
...
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Account account = accountRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));
return new UserAccount(account);
}
...
}
문제는 @WithMockUser 애노테이션으로 테스트하면 이 커스텀 UserDetailsService를 사용할 수 없다는것...
그래서 @WithUserDetails를 사용하게 되었는데, 또 @WithUserDetails는 @BeforeEach 메소드가 실행되기 전에 SecurityContext를 생성하고 UserDetails를 참조하려고 하기 때문에 @BeforeEach 메소드에서 만드는 계정을 사용할 수 없다는 것..
@BeforeEach 대신 @PostConstruct를 사용할 수 있지만 실패
@BeforeAll 을 대신 사용할 수 있지만 @BeforeEach를 사용하고 싶으므로 패스
결국 setupBefore = TestExecutionEvent.TEST_EXECUTION 이라는 옵션을 사용하면 @BeforeEach 메소드 후에 UserDetails를 참조하게 된다는걸 한참 지나서 알게됨. 근데 이 옵션도 버그가 있다는 말이 종종있다 ㄷㄷ.... 암튼 난 잘됨.
ObjectMapper
AccountDto 객체를 Json String 으로 바꾸기 위해 ObjectMapper DI
컨트롤러 리팩토링
@PostMapping("/account")
public String register(@RequestBody AccountDto accountDto) {
accountService.registerAccount(accountDto);
return "redirect:/login";
}
@PostMapping("/account/update")
public String modify(@RequestBody AccountDto accountDto,
@AuthenticationPrincipal UserAccount userAccount) {
accountService.modifyAccount(userAccount, accountDto);
return "redirect:/login";
}
요청 파라미터보다 요청 본문에 Dto 정보를 담고 싶어서 @ModelAttribute 에서 @RequestBody 로 변경.
글로벌 컨트롤러
@ControllerAdvice
public class GlobalController {
@ExceptionHandler
public ResponseEntity<?> UsedEmailExceptionHandler(UsedEmailException exception) {
return ResponseEntity.badRequest().body("can't create account, because of used email.");
}
@ExceptionHandler
public ResponseEntity<?> InvalidPasswordExceptionHandler(InvalidPasswordException exception) {
return ResponseEntity.badRequest().body("can't create account, because of password.");
}
@ExceptionHandler
public ResponseEntity<?> PermissionDeniedExceptionHandler(PermissionDeniedException exception) {
return ResponseEntity.badRequest().body("permission denied.");
}
}
일단은 테스트의 예외 처리를 위해 @ExceptionHandler 작성 수정이 필요할 듯 하다.
오늘은 요기까디~