Test - Mock 객체와 활용

인프런에서 진행한 워밍업 클럽 스터디 - 테스트 코드 / 클린 코드를 수강하며 들었던 내용을 정리합니다.

@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이

어노테이션프레임워크동작 방식주요 특징
@MockMockito가짜(mock) 객체 생성메서드 호출 시 기본적으로 아무 동작도 하지 않음
@MockBeanSpring + Mockito가짜(mock) 객체 생성Spring Context에 주입됨 (예: @Service, @Repository 등)
@SpyMockito실제 객체를 감싸는 spy 생성기본적으로 실제 객체 동작 유지, 일부 메서드만 스텁 가능
@SpyBeanSpring + Mockito실제 객체를 감싸는 spy 생성Spring Context에 주입됨
@InjectMocksMockitoMock 객체들을 테스트 대상 객체에 주입@Mock, @Spy가 붙은 객체들을 해당 필드에 자동으로 주입

테스트 코드 구현하기

✔️ 게시판 게시물에 달리는 댓글을 담당하는 Service Test ✔️ 댓글을 달기 위해서는 게시물과 사용자가 필요하다. ✔️ 게시물을 올리기 위해서는 사용자가 필요하다.

구현 필요 내용

  • 사용자가 댓글을 작성할 수 있다.
  • 사용자가 댓글을 수정할 수 있다.
  • 자신이 작성한 댓글이 아니면 수정할 수 없다.

=> User / Post / Comment 객체 필요 => User / Post / Comment 저장소 필요

  • User - Comment - 1:N 구조
  • User - Post - 1:N 구조
구분설명
테스트 대상댓글 작성 및 수정 기능을 검증하는 서비스 테스트
필요한 객체UserService, PostService, CommentService, User, Post, Comment
의존성 관리@Mock을 사용하여 UserRepository, PostRepository, CommentRepository를 Mocking
테스트 대상 객체@InjectMocks를 사용하여 CommentService를 주입

객체 초기화

@ExtendWith(MockitoExtension.class)
class CommentServiceTest {

    @Mock
    private UserRepository userRepository;
    
    @Mock
    private PostRepository postRepository;
    
    @Mock
    private CommentRepository commentRepository;
    
    @InjectMocks
    private CommentService commentService; 

    private User user;
    private Post post;
    private Comment comment;
    
  • 상기해 둔 데이터 세팅

BeforeEach

    
    @BeforeEach
    void setUp() {
        user = new User(1L, "testUser", "password");
        post = new Post(1L, "게시물 제목", "게시물 내용", user);
        comment = new Comment(1L, "댓글 내용", user, post);
        
        when(userRepository.findById(1L)).thenReturn(Optional.of(user));
        when(postRepository.findById(1L)).thenReturn(Optional.of(post));
        when(commentRepository.save(any(Comment.class))).thenAnswer(invocation -> invocation.getArgument(0));
    }
  • 공통 사용자 1명 생성 - 불필요한 사용자 세팅을 하는 것은 비효율적

2. 댓글 작성 테스트 (writeComment)

@DisplayName("사용자가 댓글을 작성할 수 있다.")
@Test
void writeComment() {
    // given
    Long userId = 1L;
    Long postId = 1L;
    String commentContent = "새로운 댓글";

    when(userRepository.findById(userId)).thenReturn(Optional.of(user));
    when(postRepository.findById(postId)).thenReturn(Optional.of(post));

    // when
    Comment savedComment = commentService.writeComment(userId, postId, commentContent);

    // then
    assertNotNull(savedComment);
    assertEquals(commentContent, savedComment.getContent());
    assertEquals(user, savedComment.getUser());
    assertEquals(post, savedComment.getPost());
}
  • given 필요한 상황 주입

3. 댓글 수정 테스트 (updateComment)

@DisplayName("사용자가 댓글을 수정할 수 있다.")
@Test
void updateComment() {
    // given
    Long userId = 1L;
    Long commentId = 1L;
    String updatedContent = "수정된 댓글 내용";

    when(commentRepository.findById(commentId)).thenReturn(Optional.of(comment));

    // when
    commentService.updateComment(userId, commentId, updatedContent);

    // then
    assertEquals(updatedContent, comment.getContent());
}

4. 댓글 수정 권한 체크 (cannotUpdateCommentWhenUserIsNotWriter)

@DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.")
@Test
void cannotUpdateCommentWhenUserIsNotWriter() {
    // given
    User anotherUser = new User(2L, "다른 사용자", "password");
    Long commentId = 1L;
    String updatedContent = "수정하려는 댓글";

    when(commentRepository.findById(commentId)).thenReturn(Optional.of(comment));

    // when & then
    assertThrows(IllegalArgumentException.class, () -> 
        commentService.updateComment(anotherUser.getId(), commentId, updatedContent)
    );
}

정리

  • @BeforeEach

    • 테스트에 필요한 User, Post, Comment를 생성
    • Mock 객체의 동작을 미리 정의
  • 댓글 작성 테스트 (writeComment)

    • 정상적으로 댓글이 저장되는지 확인
  • 댓글 수정 테스트 (updateComment)

    • 댓글 내용이 정상적으로 변경되는지 확인
  • 댓글 수정 권한 체크 (cannotUpdateCommentWhenUserIsNotWriter)

    • 작성자가 아닌 사용자가 수정하려 하면 예외 발생 여부 확인

Who is?

1의 개발로 N배의 가치, N개의 문제를 풀고 싶은 개발자