✏️ 발단
- 팀프로젝트 도중 Service 계층을 test 할 때 문제가 발생했다.
- 내가 맡은 domain 은 게시글 이였는데 게시글을 생성하기 위해서는 회원 객체가 필요했다.
- test 하기위해 회원 객체 생성후 게시글 생성과 함께 회원쪽 게시글에 add 를 했더니 NEP 가 발생
📍 게시글 entity
public static Board create(String title, String post, Member member) {
Board board = new Board();
board.title = title;
board.post = post;
board.addMember(member);
return board;
}
private void addMember(Member member) {
this.member = member;
member.getBoards().add(this);
}
📍 게시글 service
@Transactional
public Long create(Board board) {
repository.save(board);
return board.getId();
}
📍 회원 entity - 연관관계 필드
@OneToMany(mappedBy = "member")
private List<Board> boards;
📍 회원 service
public void create(String name, String password, String token) {
Member member = Member.builder()
.name(name)
.password(password)
.token(token)
.build();
memberRepository.save(member);
}
📍 test 코드
private Member createMember() {
memberService.create("A", "1234", "A");
return memberService.getMember("A");
}
private Long createBoard(String title, String post, Member member) {
Board board = Board.create(title, post, member);
return boardService.create(board);
}
@Test
void save() {
Member member = createMember();
Long boardId = createBoard("title", "post", member);
Board board = boardService.getBoard(boardId);
assertThat(board.getTitle()).isEqualTo("title");
}
✏️ 테스트 실패 원인
📍 연관관계 편의 method 에 문제가 있을 경우
- method 에 문제가 있는줄 알고 편의 mehod 를 계속해서 바꿔가며 문제를 파악했지만 아무 문제도 없었다.
📍 회원 객체 생성방법에 문제가 있을 경우
- 여러가지 반례를 생각해가며 디버그를 따라 추적해보니 회원 객체를 생성할 때 회원의 게시글 필드에서 null 값이 저장되고있다는걸 확인했다.
@Test
void name() {
memberService.create("A", "sdf", "A");
Member member = memberService.getMember("A");
assertThat(member.getId()).isEqualTo(1);
assertThat(member.getBoards()).isNull();
}
✏️ 문제 해결
📍 1차 시도 - 실패
- 회원 entity 의 게시글 필드를 new 로 초기화 시켜봤다.
- 아무런 변화도 일어나지 않고,
디버스로 확인해봐도 객체가 생성될 때 null 로 생성이 되었다.
@OneToMany(mappedBy = "member")
private List<Board> boards = new ArrayList<>();
📍 2차 시도 - 실패
- 회원 servcie 에 transactional 이 선언되어있지 않은걸 확인했다.
- 혹시 이것때문인가 싶어서 선언해주었지만 역시 상관이 없었다.
📍 3차 시도 - 실패
- 사실 build 생성은 개인프로젝트에서 사용해본적이 없어서 이렇게 봐서는 제대로 생성이 된건지 잘 가늠이 안됬다.
- 그래서 new 로 초기화를 시켜보았다.
- 아무런 변화도 일어나지 않았다.
- 조사를 해보니 new 를 선언해주지 않아도 해당 로직 자체가 new 를 선언한 것과 같은 기능을 한다고 한다.
public void create(String name, String password, String token) {
Member member = new Member.builder()
.name(name)
.password(password)
.token(token)
.build();
memberRepository.save(member);
}
📍 4차 시도 - 성공했지만 원인 파악이 안됨
- @Builder 에 대한 이해도가 낮기 때문에 확실하게 Builder 의 문제인지 확인하기 위해 않아서 생성자를 사용해 코드를 수정해봤다.
public void create(String name, String password, String token) {
Member member = new Member(name, password, token);
memberRepository.save(member);
}
- 이렇게 생성하니 npe 이 발생하지 않는다.
- 아무래도 builder 의 사용법이 잘못된것이 원인 인것같다.
📍 5차 시도 - 성공
@Builder.Default
어노테이션 속성 추가
- 해당 속성을 추가한 후 객체를 생성할경우 new 로 초기화가 된다.
@OneToMany(mappedBy = "member")
@Builder.Default
private List<Board> boards = new ArrayList()<>;