우리는 JUnit Test에서 Mock을 위한 Framework 중 Mockito를 많이 사용한다. 해당 포스팅은 Mocking을 하는 과정에서 '어 이런 상황에는 어떻게 Mock 하지?' 라고 생각할 때 해결을 하기 위해 다양한 기법에 대해 다룬다.
Mockito 기본 사용
천리길도 한 걸음부터라고 기본 사용 방법부터 보자. 먼저 의존성을 추가한다.
testImplementation 'org.mockito:mockito-core:3.11.2'
testImplementation 'org.mockito:mockito-junit-jupiter:3.11.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
이후 Mockito를 사용할 테스트 클래스에서 Mockito 확장을 가져온다.
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
}
이제 테스트에서 객체(StudyRepository)를 Mock하면 된다. 아래 예제에서는 StudyRepository를 Mock해서 findById 메서드의 인수로 1L이 들어왔을 때 study가 반환되도록 한다.
@ExtendWith(MockitoExtension.class)
public class StudyServiceTest {
@Mock
StudyRepository studyRepository;
@Test
void createStudyService() {
StudyService studyService = new StudyService(studyRepository);
Study study = new Study(1L, "Math");
when(studyRepository.findById(1L)).thenReturn(Optional.of(study));
Optional<Study> result = studyService.get(1L);
assertEquals("Math", result.get().getName());
}
}
studyService.get() 메서드 내부에서는 studyRepository.findById(1L)을 호출한다. (생략)
단위테스트에서는 Repository를 배제하고 Service가 잘 동작하는지만 테스트하므로 studyRepository.findById(1L)이 반드시 정해진 결과를 반환하도록 Mocking하는 것이다.
Argument matchers
Mocking할 메서드가 내부에서 어떤 인수로 실행될 지 모를 때가 있다. 예를 들면 스터디를 생성하는 메서드를 테스트를 해야 한다. 현재 DB의 AutoIncrement값이 몇인지 모르지만 Stubbing을 해야 한다. 이 때는 any()를 사용할 수 있다.
when(studyRepository.findById(any())).thenReturn(Optional.of(study));
any()를 사용하면 어떤 인수로 메서드가 실행되든 동일한 결과를 반환한다. 이외에도 다양한 인터페이스가 존재한다.
- anyInt(), anyBoolean(), anyFloat(), anyString() ...
- anySet(), anyMap(), anyIterable() ...
- isNull(), isNotNull() ...
- contains(), startWith(), artThat() ...
Throw
thenThrow()를 사용하면 Mocking한 객체가 특정 메서드 실행에 예외를 던지도록 할 수 있다.
when(studyRepository.findById(1L)).thenThrow(new IllegalArgumentException());
해당 구문은 mocking할 메서드가 void타입이면 사용할 수 없다. void 타입의 경우 doThrow() 메서드를 사용한다.
doThrow(new IllegalArgumentException()).when(studyRepository).validate(1L);
Consecutive calls
동일한 메서드 호출에 대해 다른 반환 값 또는 예외로 Stubbing이 필요할 떄도 있다. 아래의 코드를 보자.
when(studyRepository.findById(any()))
.thenReturn(Optional.of(study))
.thenThrow(new RuntimeException())
.thenReturn(Optional.empty());
해당 코드는 studyRepository.findById()가 처음 호출되었을 때는 Optional.of(study)를 반환한다. 그 다음 호출되었을 때는 RuntimeException을 Throw하고, 다음부터는 빈 Optional을 받게 된다.
Verify
verify를 사용하면 Mock 객체가 어떻게 사용이 되었는지 확인할 수 있다.
veriry의 인수에 times(N)를 넘겨서 studyRepository.findById(1L)이 정확히 N번 호출되지 않았으면 예외가 터진다.
verify(studyRepository, times(1)).findById(1L);
never()을 인수로 넘기면 해당 함수가 한 번이라도 실행되면 예외가 터진다.
verify(studyRepository, never()).findById(1L);
추가로 자주 사용하는 아래와 같은 기법들이 있다.
- atMostOnce()
- atLeastOnce()
- atLeast(N)
- atMost(N)
InOrder를 사용해서 순서를 검증할 수도 있다. 아래 코드를 보자.
InOrder inOrder = inOrder(studyRepository);
inOrder.verify(studyRepository).create(study);
inOrder.verify(studyRepository).findById(1L);
이 경우 studyRepository.create(study)가 studyRepository.findById(1L)보다 먼저 실행되지 않았다면 예외가 터진다.
Reference
- https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
- https://www.inflearn.com/course/the-java-application-test/
'Server > JUnit, Spock' 카테고리의 다른 글
Spring Boot - Chaos Monkey를 활용한 운영 이슈 테스트! (0) | 2022.06.11 |
---|---|
JUnit - TestContainers 사용하는 방법! (+ 장단점 비교) (3) | 2022.06.06 |
Mockito로 BDD 테스트 코드 작성하기 (BDDMockito) (0) | 2022.06.05 |
JUnit5 - 단위 테스트에서 프로퍼티 불러오기 (0) | 2022.06.02 |
JUnit5 - Parameterized Tests (0) | 2022.05.29 |