-
Service Layer의 단위테스트 작성 : (1) Mockito ①Spring 2022. 1. 28. 09:40
서비스 계층을 테스트 하기 위해서 실제 Repository를 DI하게 된다면 단위테스트를 작성하기 위한 F.I.R.S.T원칙을 위배하게 된다.
- Fast : @SpringBootTest 어노테이션이 해당 어플리케이션이 모든 빈을 Ioc 컨테이너에 등록하고 테스트를 진행하므로 테스트가 느려진다.
- Independent : repository와 의존관계를 갖는 테스트 를 작성하게 된다면 테스트 대상이 Service계층의 테스트는 Repository에 의존적이 되므로 독립적인 테스트작성이 어려워 진다.
- Repeatable : DB에 의존적인 테스트는 한번 ID가 생기면 중복되므로 반복 테스트가 어렵다.
테스트 어노테이션
@SpringBootTest 통합테스트, 전체 Bean전체 @WebMvcTest 단위테스트, MVC 테스트 MVC와 관련된 Bean @DataJpaTest 단위테스트, JPA 테스트 JPA와 관련된 Bean @RestClientTest 단위테스트, REST API 테스트 일부 Bean @JsonTest 단위테스트, Json 테스트 일부 Bean @SpringBootTest
@RunWith(SpringRunner.class)와 함께 통합테스트를 위한 스프링 부트 테스트 어노테이션으로 어플리케이션 실행 설정을 바꿔서 테스트 하는 것이 가능하며, 실제 구동되는 어플리케이션과 동일한 컨텍스트를 가져 일대일 대응 수준의 테스트가 가능하다. 따라서 테스트를 실행하기 위해 어플리케이션 설정을 모두 로드하기 때문에 규모가 클수록 느리게 작동된다.
Mokito
테스트에 들어가는 객체의 동작을 직접 제어할 수 있도록 하는 가짜객체 를 지원하는 테스트 프레임 워크이다. Mock객체의 의존성을 주입함으로서 분리되고 독립적인 테스트 코드 작성이 가능해진다. SpringBootTest에서 Mockito를 사용하고 싶을 때에는 @ExtendWith(MockitoExtension.class) 어노테이션을 추가하여 사용하도록 한다.
Mock 객체 의존성 주입
가짜 객체를 사용하기 위해서는 다음과 같은 어노테이션을 사용한다.
- @Mock : Mock 객체를 만들어 반환해주는 어노테이션으로 Mock으로 만들 객체에 붙여준다.
- @Spy : 지정해주지 않은 부분은 그대로 객체를 사용하도록 하는 어노테이션
- @InjectMocks : Mock, Spy로 생성된 가짜 객체를 주입시켜 사용할 객체에 붙여주는 어노테이션 (테스트 대상)
가짜 객체를 주입했다면 다음과 같은 메소드를 사용하여 가짜 결과를 반환하도록 지정해준다. ( = Stub )
- doReturn() : Mock객체가 특정한 값을 반환 ( = thenReturn() )
- doNothing() : Mock객체가 아무것도 반환하지 않음
- doThrow() : Mock 객체가 예외를 발생시킴
- given(가짜객체.메소드)
- willReturn() : 특정 메소드 실행시 특정 값 리턴
- willThrow() : 특정 메소드 실행시 예외 발생
doReturn(java.util.Optional.of(activity)).when(activityRepository).findById(anyInt()); when(activityRepository.findById(anyInt())).thenReturn(java.util.Optional.of(activity));
여기서 doReturn과 thenReturn의 차이는 다음과 같다.
- doReturn() : 실제 메소드를 호출하지 않으면서 리턴값을 임의로 정의 ( 실제 메소드를 호출하지 않기 때문에 대상 메서드에 문제가 있어도 알 수 없다)
- thenReturn() : 실제 메소드를 호출하지만 임의로 리턴값을 정의 ( 실제 메소드를 호출하기 때문에 메소드 작업이 오래 걸릴 경우 시간이 길어지지만 대상 메소드에 문제가 존재한다면 알 수 있다 ( 이것을 쓰도록 하자) )
Mock 객체 적용
@RunWith(SpringRunner.class) @ExtendWith(MockitoExtension.class) @SpringBootTest @Transactional class UserActivityServiceTests { @InjectMocks private UserActivityService userActivityService; @MockBean private UserActivityRepository userActivityRepository; @MockBean private ActivityRepository activityRepository; @MockBean private UserRepository userRepository; //... @Test @DisplayName("주어진 상태에 따른 새로운 사용자 액티비티를 생성하고 사용자 레벨을 조정한다.") void create_user_activity() { when(userRepository.findById(anyInt())).thenReturn(java.util.Optional.of(user)); doReturn(java.util.Optional.of(activity)).when(activityRepository).findById(anyInt()); // when(activityRepository.findById(anyInt())).thenReturn(java.util.Optional.of(activity)); UserActivityResponse response = userActivityService.create(userActivityRequest); //then assertEquals... } @Test @DisplayName("범위 밖의 점수로는 리뷰를 생성하지 않는다.") void 예외경우 테스트(){ assertThatThrownBy(() -> reviewService.create(request)) .isInstanceOf(InvalidRatingException.class); } }
위와 같이 가짜 객체를 사용할 대상에 @Mock 어노테이션을, 가짜 객체를 주입할 대상에 (테스트 할 대상) @InjectsMocks를 해줌으로서 독립적이고 반복가능한 테스트를 작성할 수 있다.
( 참고한 사이트 )
https://byeongyeon.tistory.com/85
https://pangtrue.tistory.com/228
https://royleej9.tistory.com/entry/Mockito-doReturn-thenReturn
https://jojoldu.tistory.com/226
https://stackoverflow.com/questions/3459287/whats-the-difference-between-a-mock-stub
https://stackoverflow.com/questions/61613131/testing-service-layer-in-spring-boot-using-mockito
https://stackoverflow.com/questions/51053469/unit-tests-how-to-mock-repository-using-mockito
https://velog.io/@hellonayeon/spring-boot-service-layer-unit-testcode
https://velog.io/@dnjscksdn98/JUnit-Mockito-Verify-Method-Calls
'Spring' 카테고리의 다른 글
Service Layer의 단위테스트 작성 : (2) Fake 객체 (0) 2022.02.03 Service Layer의 단위테스트 작성 : (1) Mockito ② (0) 2022.02.03 예외처리전략 ( ExceptionHandler ) 2 (0) 2022.01.13 JPA 엔티티에 생성 시점, 수정 시점 Timestamp 추가하기 ( + Auditing) (0) 2022.01.13 JPA getById() 와 findById()의 차이 (0) 2022.01.12