문제 상황
Kotlin에서 Mockito ArgumentCaptor을 사용하기 위해서 Java에서 쓰던 방법과 같은 방법을 사용하면 문제가 생긴다.
@Test
fun testLoginSuccessMockArgumentCaptor() {
val repositorySuccessResult = LoginRepositoryResult.Success("test_token")
Mockito.`when`(loginRepository.login(userName = "test", password = "test"))
.thenReturn(repositorySuccessResult)
val result = loginUseCase.logIn(userName = "test", password = "test")
val argumentCaptor: ArgumentCaptor<String> = ArgumentCaptor.forClass(String::class.java)
Mockito.verify(loginRepository, times(1)).login(argumentCaptor.capture(), argumentCaptor.capture())
assertEquals(argumentCaptor.allValues[0], "test")
assertEquals(argumentCaptor.allValues[1], "test")
assertEquals(LoginUseCaseResult.Success("test_token"), result)
}
NullPointerException이 발생하는데 이유는 Java는 Nullable한 값을 사용하지만 Kotlin은 그렇지 않기 때문이다.
java.lang.NullPointerException: argumentCaptor.capture() must not be null
at lecture4.LoginUseCaseTest.testLoginSuccessMockArgumentCaptor(LoginUseCaseTest.kt:50)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
첫번째 해결 방법
이 문제를 해결하려면 capture()시 non-Nullable 하게 만들어야 한다. 따라서 해결하기 위해서는 코드에 non-Nullable하게 값을 가져오는 함수를 추가하면 된다. 아래 capture() 함수를 코드에 추가하자.
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
이후 코드를 아래와 같이 바꿔야 한다. 위 capture가 argumentCaptor을 감싸도록 한다.
@Test
fun testLoginSuccessMockArgumentCaptor() {
...
Mockito.verify(loginRepository, times(1)).login(capture(argumentCaptor), capture(argumentCaptor))
...
}
이제 테스트를 돌려보자. 테스트가 정상 통과됨을 볼 수 있다.
하지만 위 해결 방법은 공식적으로 제공하는 방법이 아니다. 따라서 웬만하면 아래의 해결 방법을 쓰도록 하자.
두번째 해결 방법
Mockito에서 공식적 API로 위 문제를 해결하는 방법을 제공한다.
이를 사용하기 위해서는 Mockito의 dependency를 바꿔야 한다. Mockito library를 mockito-kotlin을 사용하도록 변경한다. 기존 Mockito 라이브러리(mockito-inline 등)는 지운다.
testImplementation 'org.mockito.kotlin:mockito-kotlin:4.1.0'
이후 다음과 같이 argumentCaptor<String>()을 사용해 ArgumentCaptor을 생성한 후, 기존대로 argumentCaptor.capture()을 사용하면 문제가 해결된다.
@Test
fun testLoginSuccessMockArgumentCaptor() {
...
val argumentCaptor = argumentCaptor<String>()
Mockito.verify(loginRepository, times(1)).login(argumentCaptor.capture(), argumentCaptor.capture())
assertEquals(argumentCaptor.allValues[0], "test")
assertEquals(argumentCaptor.allValues[1], "test")
...
}
실행해보면 결과가 정상적으로 출력되는 것을 확인할 수 있다.
'Unit Testing' 카테고리의 다른 글
코드 작성 시 정적 변수와 정적 메서드 사용을 지양해야 하는 이유 알아보기 (0) | 2022.12.24 |
---|---|
MockitoJUnitRunner 사용해 Mockito 코드 깔끔하게 만들기 : @Mock (0) | 2022.12.23 |
[Mockito] ArgumentCaptor 사용해 객체의 interaction 기록하기 (0) | 2022.12.21 |
[Mockito] when 사용법 한 번에 정리하기 : thenReturn, thenAnswer, doThrow (0) | 2022.12.20 |
[Unit Testing] Mockito 사용해 Test Double 만들기 (0) | 2022.12.19 |