@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);
}
}
댓글을 작성해보세요.