Day 18 Mission [Layered Architecture 에 대하여 정리]
1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다.
1. @Mock vs @Spy (Mockito)
이 두 개는 Mockito에서 제공하는 어노테이션이고, 단위 테스트에서 가짜 객체(Mock 또는 Spy)를 생성할 때 사용된다.
@Mock
완전히 가짜(Mock) 객체를 생성해서 메서드 호출 시 기본적으로
null
,0
,false
같은 값을 반환한다.호출 기록을 확인할 수 있고, 특정 동작을
when(...).thenReturn(...)
으로 지정 가능하다.실제 객체의 메서드는 호출되지 않는다.
예제:
@Mock
private UserRepository userRepository;
@Test
void testMock() {
when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "John")));
Optional<User> user = userRepository.findById(1L);
System.out.println(user.get().getName()); // John
}
@Spy
실제 객체를 감싸는(Mock하지 않는) 가짜 객체(Spy) 생성한다.
기본적으로 실제 객체의 메서드를 실행하지만, 특정 메서드는
when(...).thenReturn(...)
을 사용하여 동작을 변경할 수 있다.일부 동작만 모킹하고 싶을 때 유용하다.
예제:
@Spy
private List<String> spyList = new ArrayList<>();
@Test
void testSpy() {
spyList.add("one");
spyList.add("two");
when(spyList.size()).thenReturn(100); // 특정 메서드만 모킹
System.out.println(spyList.get(0)); // one (실제 메서드 호출)
System.out.println(spyList.size()); // 100 (모킹된 값 반환)
}
2. @MockBean vs @SpyBean (Spring Boot)
이 두 개는 Spring Boot에서 제공하는 어노테이션이다.
Spring 컨테이너에 있는 빈(Bean)을 Mock 또는 Spy로 대체하는 역할을 한다.
@MockBean
Spring 컨텍스트에서 기존에 등록된 빈(Bean)을 Mock 객체로 교체.
모든 메서드는 기본적으로
null
,0
,false
를 반환한다.@Mock
과 비슷하지만, Spring 컨테이너에 등록된 Bean을 대상으로 한다는 차이점이 있다.
예제:
@SpringBootTest
class MyServiceTest {
@MockBean
private UserRepository userRepository;
@Autowired
private MyService myService;
@Test
void testMockBean() {
when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "John")));
User user = myService.getUserById(1L);
System.out.println(user.getName()); // John
}
}
✅MockBean을 사용하면 UserRepository
의 실제 빈이 Mock으로 대체됨.
@SpyBean
Spring 컨텍스트에서 기존에 등록된 빈(Bean)을 Spy 객체로 교체된다.
기본적으로 실제 객체의 메서드를 실행하지만, 특정 메서드는
when(...).thenReturn(...)
을 사용해 동작을 변경할 수 있다.@Spy
와 비슷하지만, Spring 컨테이너의 실제 Bean을 대상으로 한다는 점이 다르다.
예제:
@SpringBootTest
class MyServiceTest {
@SpyBean
private UserService userService; // 기존 Bean을 Spy로 감싸서 일부 메서드만 Mock 가능
@Test
void testSpyBean() {
when(userService.getUserName(1L)).thenReturn("Mocked Name");
System.out.println(userService.getUserName(1L)); // Mocked Name (모킹된 메서드)
System.out.println(userService.getUserCount()); // 실제 메서드 실행
}
}
✅SpyBean을 사용하면 UserService
의 실제 빈이 Spy로 감싸짐.
3. @InjectMocks
@Mock
이나@Spy
로 생성된 객체들을 자동으로 주입해 줌.테스트 대상 클래스에 의존하는 객체들을 자동으로 설정해 주기 때문에,
new
키워드 없이 의존성을 주입할 수 있음.@Mock
또는@Spy
로 선언된 객체를@InjectMocks
가 붙은 객체의 생성자, 필드, setter 등을 이용해 주입함.
예제:
class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public String getUserName(Long id) {
return userRepository.findById(id).map(User::getName).orElse("Unknown");
}
}
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService; // userRepository가 자동 주입됨
@Test
void testInjectMocks() {
when(userRepository.findById(1L)).thenReturn(Optional.of(new User(1L, "Alice")));
System.out.println(userService.getUserName(1L)); // Alice
}
}
✅userService
를 직접 new
로 생성하지 않아도 @InjectMocks
가 @Mock
된 userRepository
를 자동으로 주입해 준다.
2. 아래 3개의 테스트가 있습니다.
내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?
(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)
댓글이 목적이니 댓글 이외의 통합할수 있는 부분은 통합처리
@BeforeEach
void setUp() {
1-1. 2-1. 3-1. 사용자 생성에 필요한 내용 준비
1-3. 2-3. 3-5. 게시물 생성에 필요한 내용 준비
1-2. 2-2. 3-2. 사용자 생성
1-4. 2-4. 3-6. 게시물 생성
}
@DisplayName("사용자가 댓글을 작성할 수 있다.")
@Test
void writeComment() {
// given
1-5. 댓글 생성에 필요한 내용 준비
// when
1-6. 댓글 생성
// then
검증
}
@DisplayName("사용자가 댓글을 수정할 수 있다.")
@Test
void updateComment() {
// given
2-5. 댓글 생성에 필요한 내용 준비
2-6. 댓글 생성
// when
2-7. 댓글 수정
// then
검증
}
@DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.")
@Test
void cannotUpdateCommentWhenUserIsNotWriter() {
// given
3-3. 사용자2 생성에 필요한 내용 준비
3-4. 사용자2 생성
3-7. 사용자1의 댓글 생성에 필요한 내용 준비
3-8. 사용자1의 댓글 생성
// when
3-9. 사용자2가 사용자1의 댓글 수정 시도
// then
검증
}
댓글을 작성해보세요.