팀 프로젝트를 하면서 테스트 코드 작성에 문제가 발생하였다.
협업을 위해 도메인별로 파트를 나눴었고, 내가 맡은 도메인은 여러 도메인과 얽혀있어 독립적이지 않았다.
따라서 내가 맡은 도메인 제외, 연관 관계에 있는 모든 엔터티들을 구현하고 실제로 jpa repository를 사용하기에는 무리가 있었다.
그래서 mock repository를 사용한 service test를 구현하고자 mockito를 사용하여 보았다.
@ExtendWith(MockitoExtension.class)
class ReservationServiceTest {
@InjectMocks
ReservationServiceImpl reservationService;
@Mock
ReservationRepository reservationRepository;
우선 test에서 mock 객체를 사용하기 위하여 @ExtendWith(MockitoExtension.class)
어노테이션을 클래스에 달아준다.
@Mock
어노테이션은 필요한 의존성 중 모든 메소드를 stub할 mock으로 사용할 객체에 달아준다.
@InjectMocks
어노테이션은 mock으로 만든 의존성들을 주입받을 객체에 달아준다.
이외에도 @Spy
등이 있는데 @Spy는 선택적으로 stub 하고 싶을 때 사용한다.
@Test
void 예약_취소_시_취소_가능시간이_지났으면_예외가_발생한다() {
// given
String hour = String.valueOf(LocalDateTime.now().getHour());
String startTime = hour.length() == 1 ? "0" + hour + "00" : hour + "00";
Reservation reservation = Reservation.builder()
.id(1L)
.date(LocalDate.now())
.startTime(startTime)
.status(ReservationStatus.RESERVED)
.build();
when(reservationRepository.findById(reservation.getId())).thenReturn(
Optional.of(reservation));
// when & then
assertThrows(ReservationCancelTimeoutException.class,
() -> reservationService.cancelReservation(reservation.getId()));
}
사용법은 다음과 같다.
when()
안에 mock의 stub할 메소드를 넣는다.- 메소드의 매개변수에 해당되는 값이 전달될 때만 동작한다.
- 매개변수에 any()를 사용하면 type만 일치하면 모든 매개변수에 동작한다.
thenReturn()
에 stub 메소드가 호출되면 반활할 값을 넣는다.
Reservation은 13개의 필드가 존재하지만, mock repository를 사용하여 service의 cancelReservation()
사용하는 4개의 필드만 사용할 수 있었다.
만약 실제 repository를 사용하여 작성하였다면, Reservation을 저장하기 전에 연관되는 User, Hairshop, Designer, Menu 엔터티를 생성하고 Reservation의 not null인 모든 필드들을 추가했어야 됐다.
우리는 저 로직에서 repository의 findById()
가 어떤 값을 반환해야되는지 알고 있기 때문에 이렇게 간단하게 stub으로 구현할 수 있는 것이다.
물론 mock을 사용하는 것은 불완전하고 작성하는 사람이 실수를 하면 테스트에선 통과했지만 실제 서비스에서는 오류가 발생할 수 있다.
따라서 예외 상황은 없는지 꼼꼼하게 생각하여야 하며, mock으로 사용한 객체또한 test를 작성하여 메소드 실행 시 예상한 결과가 실행되는 지 확인해야 한다.