Server/JUnit, Spock

Mockito를 제대로 사용하는 방법들! (단위테스트)

JaeHoney 2022. 6. 4. 21:07
반응형

우리는 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

 

 

 

 

 

 

 

반응형