계좌 시스템 개발 프로젝트
1. inner class 활용 방법
public class CreateAccount {
public static class Request{
}
public static class Response{
}
}
@PostMapping("/account")
public String createAccount(
@RequestBody @Valid CreateAccount.Request account) {
accountService.createAccount();
return "success";
}
2. static 메소드의 활용 방법
특정 엔티티에서 특정 dto로 변환할때 생성자를 사용해서 변환 하는것보다 가독성이 좋고 안전하게 생성할수 있다.
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class Response{
private Long userId;
private String accountNumber;
private LocalDateTime registeredAt;
public static Response from(AccountDto accountDto){
return Response.builder()
.userId(accountDto.getUserId())
.accountNumber(accountDto.getAccountNumber())
.registeredAt(accountDto.getRegisteredAt())
.build();
}
}
3. MockBean
https://jojoldu.tistory.com/226
위 링크에서 가져온 내용이다
Mock은 껍데기만 있는 객체를 얘기합니다.
인터페이스의 추상메소드가 메소드 바디는 없고 파라미터 타입과 리턴타입만 선언된 것처럼, Mock Bean은 기존에 사용되던 Bean의 껍데기만 가져오고 내부의 구현 부분은 모두 사용자에게 위임한 형태입니다.
즉, 해당 Bean의 어떤 메소드가 어떤 값이 입력 되면 어떤 값이 리턴 되어야 한다는 내용 모두 개발자 필요에 의해서 조작이 가능합니다.
요약하면 어떤 입력값이 주어질때 리턴값을 개발자가 조작해 테스트를 하도록 할수 있는것이다 사용하는 이유는 하나의 서비스 메소드를 테스트 하기위해 불필요한 테스트 데이터를 생성해주어야 하기때문이다
*Mock vs MockBean 차이
요약하면 @MockBean와 @Mock의 차이점은 스프링 애플리케이션 컨텍스트의 로딩 유무에 따라 다르다는 것이다
즉 mock 객체가 스프링 빈에 등록되어야 한다면 @MockBean 아니라면 @Mock을 사용하면된다
*참고
스프링 컨테이너 관련 개념
https://ittrue.tistory.com/220
요약하면 스프링 컨테이너엔 두 종류의 인터페이스가 있다 ( BeanFactory , application context)
스프링 컨테이너는 빈팩토리의 기능을 상속 받아 확장한 애플리케이션 컨텍스트를 주로 사용한다
Ioc , Di 관련 개념
https://devmango.tistory.com/174
https://lucas-owner.tistory.com/39
요약하면 Ioc가 적용된 대표적인 기술은 프레임워크이다 (내가 작성한 코드가 직접 제어의 흐름에 담당한다면 라이브러리)
Ioc의 역할을 담당하는 것이 스프링 컨테이너이다
4. @Builder
https://velog.io/@rlvy98/Spring-Lombok-Builder%EB%9E%80
아래 내용은 두번째 링크 내용이다
@Builder 어노테이션을 클래스에 적용하면 해당 클래스에 대한 빌더 클래스를 자동으로 생성합니다. 이 빌더 클래스는 객체의 생성과 속성 설정을 담당하며, 메서드 체인을 통해 속성 값을 설정할 수 있습니다.
빌더 패턴을 통해 가질수 있는 이점은 유연한 객체 생성, 가독성과 유지보수성, 불변 객체 생성, 선택적 매개변수 처리 등이 가능하다
*@Builder 와 @ NoArgsConstructor 어노테이션을 같이 사용할때 @AllArgsConstructor가 있어야 하는 이유 :
@Builder는 기본적으로 모든 멤버 변수를 파라미터로 받는 생성자가 필요하다 그 이유는 build() 메소드에서 앞서 말한 생성자를 통해 리턴 해야하기 때문이다 그런데 @Builder는 생성자가 없으면 모든 멤버 변수를 파라미터로 받는 생성자를 생성하고, 없으면 따로 생성자를 생성하지 않는다 @ NoArgsConstructor 어노테이션을 통해 생성자가 있는 상태이기 때문에 @Builder는 모든 멤버 변수를 파라미터로 받는 생성자를 생성하지 않게 되기 때문에 에러가 발생한다
참고링크
5. ArgumentCaptor
//given
AccountUser user = AccountUser.builder()
.id(12L)
.name("Pobi").build();
given(accountUserRepository.findById(anyLong()))
.willReturn(Optional.of(user));
given(accountRepository.findFirstByOrderByIdDesc())
.willReturn(Optional.of(Account.builder()
.accountNumber("1000000012").build()));
//1.
given(accountRepository.save(any()))
.willReturn(Account.builder()
.accountUser(user)
.accountNumber("1000000013").build());
//2.
ArgumentCaptor<Account> captor = ArgumentCaptor.forClass(Account.class);
//when
AccountDto accountDto = accountService.createAccount(1L, 1000L);
//then
//3.
verify(accountRepository, times(1)).save(captor.capture());
assertEquals(12L,accountDto.getUserId());
//4.
assertEquals("1000000013", captor.getValue().getAccountNumber());
현재 accountNumber가 저장될때 가장 마지막 accountNumber 에서 1씩 증가한 값이 저장이 되고 있다 그걸 확인 할수 있는 방법이 ArgumentCaptor를 사용하는 방법이다
1. 어떤 accountNumber를 저장(save)하는지 확인하기위해 ArgumentCaptor 사용
2. accountRepository가 저장하는 것은 account즉 타입은 account
3. 저장하는 횟수는 1번임을 검증하고 그 때 저장하는 값(account)을 캡쳐
.4 캡쳐한 값(accountNumber)을 검증
6. successDeleteAccount 테스트 코드 에러
테스트 코드 작성했는데 cannot deserialize from object value 라는 에러가 발생해 구글링을 통해 아래 관련 링크 참고했다
https://azurealstn.tistory.com/74
원인은 JSON 데이터를 Java Object로 변환하기 위해서 비어있는 생성자가 필요했지만 jackson library는 빈 생성자가 없는 모델을 생성하는법을 모르기 때문이다 따라서 직접 빈 생성자를 추가해도 되고 @NoArgsConstructor 어노테이션을 추가해 해결할수 있다
7. 컨트롤러 URL 매핑 오류
Failed to load ApplicationContext : Error creating bean with name 'requestMappingHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'transactionController' method
확인해보니 컨트롤러의 URL을 같게 설정해주어 발생한 오류였다 아마 컨트롤러의 코드를 작성할때 복붙을 사용하다보니 만나게 된것같다.. ;