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

 

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

 

@Mock

  • @Mock은 Mockito에서 제공하는 애노테이션으로, 특정 클래스나 인터페이스의 가짜 객체(Mock)를 생성한다.

  • Spring 컨텍스트와는 무관하며, 순수하게 단위 테스트를 위해 사용된다.

  • 테스트 대상 객체가 의존하는 객체를 가짜로 만들어 테스트를 고립시키고자 할 때 활용된다.

@MockBean

  • @MockBean은 Spring Boot에서 제공하는 애노테이션으로, Spring 컨텍스트에 등록된 Bean을 Mock 객체로 교체한다.

  • 통합 테스트에서 특정 Bean의 실제 동작을 대체해야 할 때 사용된다.

  • 주로 서비스 계층의 일부 Bean을 Mock으로 교체해 컨트롤러 테스트를 진행하는 경우에 적합하다.

@Spy

  • @Spy는 실제 객체를 기반으로 부분적으로 Mocking을 할 수 있게 해주는 Mockito 애노테이션이다.

  • 객체 전체를 Mock으로 만드는 것이 아니라, 특정 메서드만 가짜 동작을 설정할 수 있다. 나머지 메서드는 원래 동작을 그대로 유지한다.

  • 실제 객체의 일부 동작만 변경하거나 추적하려는 경우에 적합하다.

@SpyBean

  • @SpyBean은 Spring Boot에서 제공하는 애노테이션으로, Spring 컨텍스트에서 관리되는 Bean을 Spy로 교체한다.

  • @Spy와 비슷하지만, Spring 컨텍스트 내에서 관리되는 Bean에 대해 부분적인 Mocking이 가능하다는 점에서 차이가 있다.

  • Spring 환경에서 특정 Bean의 일부 메서드만 가짜로 설정하고 나머지는 원래 동작을 유지하고자 할 때 사용된다.

@InjectMocks

  • @InjectMocks는 테스트 대상 클래스에 필요한 의존성을 자동으로 주입해주는 Mockito 애노테이션이다.

  • 생성자, 필드, 또는 setter를 통해 Mock이나 Spy 객체를 자동으로 주입한다.

  • 테스트 대상 클래스가 여러 의존성을 가지고 있을 때 이를 효율적으로 설정할 수 있다.

  • Mock이나 Spy 객체를 단순히 생성하는 것을 넘어, 이들을 테스트 대상 클래스와 연결해주는 역할을 한다.

이 애노테이션들은 각각의 목적과 상황에 맞게 사용되며, 단위 테스트에서는 주로 @Mock@InjectMocks가 사용되고, 통합 테스트에서는 @MockBean@SpyBean이 적합한 경우가 많다. @Spy는 실제 객체의 일부만 Mocking해야 하는 상황에서 유용하다.

 

 

 

2. 테스트 코드 리팩토링

공통 항목(@BeforeEach)

테스트 코드에서 중복되는 사용자 및 게시물 생성 로직을 @BeforeEach로 이동하여 공통화할 수 있다.

 

class CommentServiceTest {

    private User user;
    private Post post;

    @BeforeEach
    void setUp() {
        // 사용자 생성에 필요한 내용 준비 및 사용자 생성
        user = createUser();

        // 게시물 생성에 필요한 내용 준비 및 게시물 생성
        post = createPost(user);
    }

    @DisplayName("사용자가 댓글을 작성할 수 있다.")
    @Test
    void writeComment() {
        // given
        Comment comment = prepareComment(post, user);

        // when
        commentService.writeComment(comment);

        // then
        assertThat(commentRepository.findById(comment.getId())).isPresent();
    }

    @DisplayName("사용자가 댓글을 수정할 수 있다.")
    @Test
    void updateComment() {
        // given
        Comment comment = prepareComment(post, user);
        commentService.writeComment(comment);

        String updatedContent = "Updated content";

        // when
        commentService.updateComment(comment.getId(), updatedContent);

        // then
        assertThat(commentRepository.findById(comment.getId()).get().getContent()).isEqualTo(updatedContent);
    }

    @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.")
    @Test
    void cannotUpdateCommentWhenUserIsNotWriter() {
        // given
        User anotherUser = createAnotherUser();
        Comment comment = prepareComment(post, user);
        commentService.writeComment(comment);

        String updatedContent = "Updated content";

        // when & then
        assertThrows(UnauthorizedException.class, () -> 
            commentService.updateCommentAsUser(anotherUser, comment.getId(), updatedContent)
        );
    }

    private User createUser() {
        // 사용자 생성 로직 구현
        return new User("user1", "password");
    }

    private User createAnotherUser() {
        // 다른 사용자 생성 로직 구현
        return new User("user2", "password");
    }

    private Post createPost(User user) {
        // 게시물 생성 로직 구현
        return new Post("게시물 제목", "게시물 내용", user);
    }

    private Comment prepareComment(Post post, User user) {
        // 댓글 생성 로직 구현
        return new Comment("댓글 내용", post, user);
    }
}

댓글을 작성해보세요.


채널톡 아이콘