@Transactional
public Long createPostComment(Long postId, Long userId, PostCommentRequest createDTO) {
PostComment postComment = buildPostComment(userId, postId, createDTO);
if (createDTO.getParentCommentId() != null) { // 대댓글일시
PostComment parentComment = postCommentRepository.findById(createDTO.getParentCommentId())
.orElseThrow(() -> new CustomException(HttpStatus.NOT_FOUND, ResultCode.POST_COMMENT_NOT_FOUND));
parentComment.addCommentReply(postComment);
}
PostComment savedComment = postCommentRepository.save(postComment);
return savedComment.getId();
}
위 Service 메소드의 역할
- postComment 엔티티 생성
- 댓글 대댓글 구분 후 대댓글일시 부모댓글에 자식 댓글 추가
- 댓글 저장
단위테스트를 작성하고 싶습니다. createPostCommet 메소드에서 검증해야 할 것이 어떤게 있을까요?
- postComment엔티티를 잘 생성하는지?
- 대글 대댓글 구분 후 대댓글일시 부모댓글에 자식 댓글을 잘 추가하는지?
- 댓글을 잘 저장하는지?
- request의 parentComment가 null일시 if문 안에 로직이 잘 작동하는지?
그런데 위 세가지는 너무 간단해서 로직이 생각한대로 동작할 것 같아 테스트를 해야하는지 의문입니다. 그래서 저는 return 값이 잘 나오는지 확인하기 보다 save가 제대로 호출됐는지 검증하려고 합니다.(createPostComment의 테스트 코드의 의무가 이것을 검증하는게 맞는지 의문입니다...)
save가 잘호출됐는지 확인하기 위해 BDD로 작성하였습니다.
public class PostCommentWriteServiceTest {
@Mock
private PostCommentRepository postCommentRepository;
@Mock
private UserService userService;
@InjectMocks
private PostCommentWriteService postCommentWriteService;
@Test
@DisplayName("댓글 생성 성공")
void test1() {
// Given
Long postId = 1L;
Long userId = 1L;
PostCommentRequest createDTO = new PostCommentRequest();
createDTO.setContents("test contents");
createDTO.setParentCommentId(null);
PostComment mockPostComment = new PostComment();
given(userService.getUser(anyLong())).willReturn(new User());
given(postCommentRepository.save(any(PostComment.class))).willReturn(mockPostComment);
// When
Long result = postCommentWriteService.createPostComment(postId, userId, createDTO);
// Then
then(postCommentRepository).should(never()).findById(anyLong());
then(postCommentRepository).should(times(1)).save(any(PostComment.class));
assertThat(result).isNotNull();
}
}
'User()' has protected access in 'com.tripmate.tripmate.user.domain.User'
given(userService.getUser(anyLong())).willReturn(new User()); 부분에서
에러가 발생했습니다.
에러 이유는 위 부분 때문입니다.
access = AccessLevel.PROTECTED를 쓴 이유가 궁금하다면 이전 글을 확인해보면 됩니다.
@NoArgsConstructor(access = AccessLevel.PROTECTED) 쓰는 이유
@Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) public class PostComment extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") pr
jsw5913.tistory.com
- access = AccessLevel.PROTECTED를 Public으로 바꿀까?
- 아님 모의객체를 사용할까
access = AccessLevel.PROTECTED쓴 이유는 명확하기 때문에 Public으로 바꾸지 않을 것입니다. 그럼 모의객체를 사용해서 해결하겠습니다.
수정 코드
@Test
@DisplayName("댓글 생성 성공")
void test1() {
// Given
Long postId = 1L;
Long userId = 1L;
PostCommentRequest createDTO = new PostCommentRequest();
createDTO.setContents("test contents");
createDTO.setParentCommentId(null);
given(userService.getUser(anyLong())).willReturn(mock(User.class));
given(postRepository.findById(anyLong())).willReturn(Optional.of(mock(Post.class)));
given(postCommentRepository.save(any(PostComment.class))).willReturn(mock(PostComment.class));
// When
Long result = postCommentWriteService.createPostComment(postId, userId, createDTO);
// Then
then(postCommentRepository).should(never()).findById(anyLong());
then(postCommentRepository).should(times(1)).save(any(PostComment.class));
}
@Test
@DisplayName("request Id값이 1일때 대댓글 생성 성공")
void test2() {
// Given
Long postId = 2L;
Long userId = 1L;
PostCommentRequest createDTO = new PostCommentRequest();
createDTO.setContents("test contents");
createDTO.setParentCommentId(1L);
given(userService.getUser(anyLong())).willReturn(mock(User.class));
given(postRepository.findById(anyLong())).willReturn(Optional.of(mock(Post.class)));
given(postCommentRepository.findById(anyLong())).willReturn(Optional.of(mock(PostComment.class)));
given(postCommentRepository.save(any(PostComment.class))).willReturn(mock(PostComment.class));
// When
Long result = postCommentWriteService.createPostComment(postId, userId, createDTO);
// Then
then(postCommentRepository).should(times(1)).findById(anyLong());
then(postCommentRepository).should(times(1)).save(any(PostComment.class));
}
코드를 수정하고 테스트가 통과됐지만, 찝찝합니다. 앞에서 언급했던 createPostCommetn메소드의 단위테스트 역할을 다 했나?가 의문이고 BDD를 이럴 때 쓰는 게 맞나 생각이 듭니다.
BDD는 사용자 시나리오 검증입니다.
현재 테스트의 given은 createPostCommet 메소드내에 있는 함수들이 가상의 값을 리턴하도록 설정한 것입니다. 사용자 시나리오가 전혀 아닙니다.
진짜 BDD는 아래와 같이 사용자 시나리오를 설정하는 거 아닐까요?
단위테스트를 작성하고 싶어 지금까지 위와 같이 작성했습니다. 위 방법이 틀리다며 mockMVC.perform()을 이용하여 직접 API를 호출하여 통합테스트를 작성해야 될까요?
혹은 미리 관련 엔티티를 세팅하고 createPostComment만 호출한다 해도 User와 Post를 미리 데이터베이스에 저장하고 마지막에 Comment가 잘 저장되었는지 확인하기 위해 데이터베이스를 사용해야 하는데 Service 단위 테스트를 하기위해 Reposiory를 쓰는 건 잘못된 거 아닌가요?
'테스트' 카테고리의 다른 글
🙊 Fixture Monkey 도입 (1) | 2024.12.19 |
---|---|
테스트코드 작성시 유념사항 (1) | 2024.04.07 |
[Trasactional] 자바 스케줄러 테스트 시도 과정 (0) | 2024.03.28 |
통합, 단위 테스트 차이 (0) | 2023.10.13 |