서비스 계층의 단위 테스트는 “이 메서드가 주어진 상황에서 의도한 대로 동작하는가?”를 검증하는 과정이다. DB나 외부 API에 의존하지 않고, Mock(가짜 객체)을 활용하여 비즈니스 로직 자체만 집중해서 테스트하는 것이 핵심이다.
먼저 우리가 테스트할 createPost() (게시글 작성) 메서드를 살펴보자.
public PostResponse createPost(CustomUserDetails userDetails, PostCreateRequest request) {
// 1. 회원 조회 (없으면 예외 발생)
Member member = memberRepository.findById(userDetails.getId())
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
// 2. 게시글 엔티티 생성
Post post = Post.builder()
.member(member)
.title(request.getTitle())
.content(request.getContent())
.build();
// 3. DB 저장 및 응답 DTO 반환
return PostResponse.from(postRepository.save(post));
}
이 메서드를 테스트하려면 크게 두 가지 상황(Case)을 고려해야 한다.
Given 에서는 createPost() 메서드 실행에 필요한 재료들을 먼저 준비해 둔다! 필요한 인자(CustomUserDetails, PostCreateRequest)를 만들고, Mock 객체들이 어떻게 행동할지(given()) 미리 정의해 준다.
@Test
@DisplayName("게시글 생성 성공")
void createPost_성공() {
// given
PostCreateRequest request = new PostCreateRequest("새 제목", "새 내용");
// memberRepository.findById()가 호출되면 가짜 member 객체를 반환하도록 설정
given(memberRepository.findById(1L)).willReturn(Optional.of(member));
// postRepository.save()가 호출되면 가짜 post 객체를 반환하도록 설정
given(postRepository.save(any(Post.class))).willReturn(post);
// when (실제 테스트할 메서드 실행)
PostResponse result = postService.createPost(userDetails, request);
// then (결과 검증)
assertThat(result.getTitle()).isEqualTo("새 제목");
assertThat(result.getContent()).isEqualTo("새 내용");
// save() 메서드가 실제로 1번 호출되었는지 검증 (중요!)
verify(postRepository, times(1)).save(any(Post.class));
}
해설
findById와 save 가 호출될 때 DB까지 가지 않도록, “위 메서드(findById(), save())가 호출되면 이거 반환해!”라고 Mock 객체에 지시를 내린다.PostResponse 의 데이터가 내가 요청한 데이터와 일치하는지(assertThat) 확인하고, 저장 로직인 save() 가 빼먹지 않고 잘 호출되었는지(verify) 확인한다.실패 Case에서는 비정상적인 상황을 가정한다. 즉, DB에서 회원을 찾았는데 결과가 없는 상황(Optional.empty())을 강제로 만들어주고, 우리가 기대한 예외(CustomException)가 제대로 터지는지 확인한다.
@Test
@DisplayName("게시글 생성 시 없는 회원이면 예외 발생")
void createPost_없는회원_예외() {
// given
PostCreateRequest request = new PostCreateRequest("새 제목", "새 내용");
given(memberRepository.findById(1L)).willReturn(Optional.empty());
// when & then
// 없는 회원으로 createPost를 호출하면 CustomException이 발생해야 한다!
assertThatThrownBy(() -> postService.createPost(userDetails, request))
.isInstanceOf(CustomException.class);
verify(postRepository, never()).save(any(Post.class));
}
해설
findById가 Optional.empty()를 반환하도록 세팅하여 예외 상황의 조건을 만든다.assertThatThrownBy를 사용하면 깔끔하다. 실행 시 우리가 지정한 CustomException이 발생하는지 검증하다.save)은 절대 실행되면 안 된다. never()를 사용해 이를 명확히 보장해 준다.