이번 시간에는 Mockito를 사용해 Test Double을 만드는 방법을 살펴볼 것이다.
Mockito를 사용하기 위한 환경 설정부터 시작하자.
환경설정
이전 글까지는 junit:junit:4.13.2 만으로 테스트가 가능했지만, 이번 글에서는 Mockito를 사용하므로 하나의 Dependency를 추가해야 한다. 바로 'org.mockito:mockito-inline:3.3.3' 이다. Kotlin을 사용한다면 mockito-inline을 추가해 주는 것이 좋다.
dependencies {
...
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito.kotlin:mockito-kotlin:4.1.0'
}
추가했으면 Load Gradle Changes 버튼을 누르자.
자 이제 환경 설정이 완료되었다. 본격적으로 Mockito를 사용해보도록 하자.
Mockito 사용하기
Mockito를 사용하기 위해 조금 클래스가 복잡해진다. LoginUseCase는 Login을 위한 UseCase 역할을 하는 레이어이다. 파라미터로 서버와 통신 역할을 수행하는 LoginRepository를 받아 로그인 요청이 들어오면 로그인을 수행한다.
class LoginUseCase(
private val loginRepository: LoginRepository
) {
fun logIn(userName: String, password: String): LoginUseCaseResult {
return when (val result = loginRepository.login(userName, password)) {
is LoginRepositoryResult.Success -> LoginUseCaseResult.Success(result.token)
is LoginRepositoryResult.Fail -> LoginUseCaseResult.Success(result.error)
}
}
}
LoginUseCaseResult는 성공(Success)와 실패(Fail)을 Wrapping하는 간단한 sealed interface이다.
sealed interface LoginUseCaseResult {
data class Success(val token: String) : LoginUseCaseResult
data class Fail(val error: String) : LoginUseCaseResult
}
LoginRepository는 서버에 로그인 요청을 하는 역할을 수행하는 인터페이스이다. 이 글에서는 Mockito를 사용해 Mock객체를 만들 것이기 때문에 class로 구현하지 않는다.
interface LoginRepository {
fun login(userName: String, password: String) : LoginRepositoryResult
}
LoginRepository의 응답 또한 성공(Success)와 실패(Fail)을 Wrapping하는 간단한 sealed interface이다.
sealed interface LoginRepositoryResult {
data class Success(val token: String) : LoginRepositoryResult
data class Fail(val error: String) : LoginRepositoryResult
}
자 테스트용 클래스, 인터페이스 설정이 완료되었다
테스트 준비하기
테스트를 준비하기 위해서는 loginUseCase와 loginRepository를 모두 초기화 해야 한다.
class LoginUseCaseTest {
private lateinit var loginUseCase: LoginUseCase
private lateinit var loginRepository: LoginRepository
@Before
fun setUp() {
loginRepository = Mockito.mock(LoginRepository::class.java)
loginUseCase = LoginUseCase(loginRepository)
}
}
loginRepository는 테스트 대상 클래스가 아니므로 Mockito.mock 메서드를 사용해 Mock 객체로 초기화 한다.
loginUseCase는 Mock된 loginRepository를 주입 받아 초기화한다.
자 이제 모든 준비가 끝났다. Mockito를 사용해 Test Double 중 Stub, Mock을 만들어보도록 하자.
Stub 만들기
Stub은 특정 요청 시 미리 정의된 데이터를 반환하는 것이다. 여기서는 LoginRepository가 userName 이 'test'이고 password도 'test'인 로그인 요청을 받았을 때 다음 객체를 반환하도록 설정한다.
val repositorySuccessResult = LoginRepositoryResult.Success("test_token")
이를 위해서는 Mockito의 `when`메서드를 사용하면 된다. `when` 의 파라미터로는 Mock객체의 Method Call이 온다. 즉, loginRepository에서 login 요청이 왔을 때 위 객체를 반환하려면 다음과 같이 작성하면 된다.
Mockito.`when`(loginRepository.login(userName = "test", password = "test"))
.thenReturn(repositorySuccessResult)
간단하게 Stub의 작성이 완료되었다.
자 이제 해당 Stub을 이용해보자. loginUseCase.logIn(userName = "test", password = "test") 이 호출되면 이는 loginRepository로 login요청을 하고 LoginRepositoryResult.Success("test_token")를 반환받는다. 그리고 이는 LoginUseCaseResult.Success("test_token")로 변환되도록 위에서 설정되었다.
val result = loginUseCase.logIn(userName = "test", password = "test")
따라서 위 코드의 result 는 LoginUseCaseResult.Success("test_token") 이다.
그러면 이제 검증을 하면 된다.
assertEquals(LoginUseCaseResult.Success("test_token"), result)
여기까지 작성 후 테스트를 수행해보자. 전체 코드는 다음과 같다.
class LoginUseCaseTest {
private lateinit var loginUseCase: LoginUseCase
private lateinit var loginRepository: LoginRepository
@Before
fun setUp() {
loginRepository = Mockito.mock(LoginRepository::class.java)
loginUseCase = LoginUseCase(loginRepository)
}
@Test
fun testLoginSuccessStub() {
val repositorySuccessResult = LoginRepositoryResult.Success("test_token")
Mockito.`when`(loginRepository.login(userName = "test", password = "test"))
.thenReturn(repositorySuccessResult)
val result = loginUseCase.logIn(userName = "test", password = "test")
assertEquals(LoginUseCaseResult.Success("test_token"), result)
}
}
그러면 다음과 같이 성공이 나오는 것을 확인할 수 있다.
Mock 만들기
Mock은 interaction을 기록한다. Mock된 LoginRepository 가 login을 1번 부르고 파라미터로 "test", "test"를 넘기는지 확인하기 위해서는 Mockito의 verify 메서드를 사용하면 된다. Mockito의 verify는 첫 인자로 Mock 객체를, 둘째 인자로 Mock객체의 특정 메서드 호출 횟수를 받는다. 그리고 마지막에 Mock객체에서 어떤 메서드가 실행되었는지를 쓰면 된다.
예를 들어 loginRepostiroy에서 login("test","test")가 1번 불렸다면 times(1)을 쓰면 되고
Mockito.verify(loginRepository, times(1)).login("test", "test")
한 번도 불리지 않았다면 다음과 같이 never()을 쓰면 된다.
Mockito.verify(loginRepository, never()).login("test", "test")
Stub 만들기 테스트에서 사용한 코드에 다음 한 줄만 추가해서 테스트 해보자.
Mockito.verify(loginRepository, times(1)).login("test", "test")
전체 코드는 다음과 같다.
@Test
fun testLoginSuccess() {
val repositorySuccessResult = LoginRepositoryResult.Success("test_token")
Mockito.`when`(loginRepository.login(userName = "test", password = "test"))
.thenReturn(repositorySuccessResult)
val result = loginUseCase.logIn(userName = "test", password = "test")
Mockito.verify(loginRepository, times(1)).login("test", "test")
assertEquals(LoginUseCaseResult.Success("test_token"), result)
}
그러면 성공한 것으로 나온다.
정리
이번 글에서는 Mockito를 간단하게 사용해보았고 앞서 살펴본 Test Double을 Mockito를 사용해 구현하면 매우 쉽게 구현할 수 있는 것을 확인했다.
다음 글부터는 Mockito를 더욱 심화해서 사용 하는 방법에 대해 다룰 것이다.
'Unit Testing' 카테고리의 다른 글
[Mockito] ArgumentCaptor 사용해 객체의 interaction 기록하기 (0) | 2022.12.21 |
---|---|
[Mockito] when 사용법 한 번에 정리하기 : thenReturn, thenAnswer, doThrow (0) | 2022.12.20 |
Test Double이란 무엇인가? Test Double의 종류, 사용법 알아보기 (0) | 2022.12.18 |
Unit Testing에서 Test Double이 필요한 이유는 무엇일까? (0) | 2022.12.17 |
Kotlin에서 사용할 수 있는 JUnit assert 종류 알아보기 : assertEquals, assertTrue, assertThrows, assertNotNull (0) | 2022.12.16 |