RetailAI Adventurers Advent Calendar 2023の7日目の記事です。
Mockito-Kotlinの基本的な使い方をまとめてみます。
Javaなどでテストダブルを作成できるMockitoというライブラリがあります。
以前、KotlinとSpring Bootを使ったサンプルアプリのテストコードを書くためにMockitoを使ってみたことがあります。せっかくKotlin用のライブラリがあるので、当時のメモをもとにMockito-Kotlinで書き直してみました。
テスト対象のコード
@Service
class UserService {
@Autowired
lateinit var repository: UserRepository
fun getUserById(id: Int): User? {
return repository.findById(id).orElse(null)
}
}
このクラスは以下のUserRepopsitory
クラスに依存しています。
JPAリポジトリを利用してデータベースにアクセスするコードです。
(自分でテスト用のクラスを作ろうとすると、オーバーライドしないといけないメソッドが多くて結構大変)
@Repository
interface UserRepository: JpaRepository<User, Int> {}
UserService
だけをテストしたいので、UserRepository
をモック化したい。
Mockit-Kotlinを使ってみる
build.gradle に以下の依存関係を追加。
testImplementation 'org.mockito.kotlin:mockito-kotlin:5.2.0'
テストクラスを作成
class UserServiceTestByKotlin {
@Mock
lateinit var mockUserRepository: UserRepository
@InjectMocks
lateinit var userService: UserService
@BeforeEach
fun init() {
MockitoAnnotations.openMocks(this)
}
}
@Mock
でmock化するクラスを指定。
@InjectMocks
でmock化するクラスに依存しているクラスを指定する。
モックを使ったテスト
@Test
fun shouldInvokeFindById() {
userService.getUserById(10)
userService.getUserById(20)
userService.getUserById(20)
verify(mockUserRepository).findById(10) ///(1)
verify(mockUserRepository, atLeast(1)).findById(10)
verify(mockUserRepository, times(3)).findById(anyInt())
}
verify()
でメソッドが呼び出された回数のテストを行う。
xx回、最大(最小)xx回などを第2引数で指定する。
///(1)
は第2引数にtimes(1)
を渡したのと同じ動作らしい。
スタブを使ったテスト
private val testUser = User(1, "taro", 33)
@Test
fun shouldReturnUserDetailFromRepository() {
whenever(mockUserRepository.findById(1))
.thenReturn(Optional.of(testUser))
val user = userService.getUserById(1)
assertThat(user!!.id).isEqualTo(1)
assertThat(user.userName).isEqualTo("taro")
assertThat(user.age).isEqualTo(33)
}
whenever()
とthenReturn()
を使う。
戻り値を指定しない場合、null
(プリミティブ型なら初期値)になっている。
@Test
fun stubDefaultValue() {
val user = userService.getUserById(2)
assertThat(user).isNull()
}
以下のようにも書ける。
mockUserRepository.stub {
on { findById(1) } doReturn Optional.of(testUser)
}
JavaでMockitoを使ったときの違いなど
when
とwhenever
Stubで値を返す値を指定するとき、Mockitoではwhen
を使う。
しかしKotlinではwhen
が予約語になっているので、whenever
を使えるようになっている。
ちなみに、ソースコードを見るとwhen
の糖衣構文らしい。
@Suppress("NOTHING_TO_INLINE")
inline fun <T> whenever(methodCall: T): OngoingStubbing<T> {
return Mockito.`when`(methodCall)!!
}
any()
whenever(mock.example(any(User::class.java))).thenReturn(11)
とか
verify(mock).example(any(User::class.java))
などが、NullPointerException
になる。
「any()
などが使えるようになる!」て堂々と書いていたのに......?と思ったが、上のコードで使っていたany()
はMockitoで定義されているものだった。
Spring Bootのプロジェクトを作ったときに追加していた依存関係
testImplementation 'org.springframework.boot:spring-boot-starter-test'
この中で読み込まれるパッケージにMockitoが含まれており、MockitoとMockito-Kotlinのany()
が衝突していたらしい。
(だから、引数を渡さないとIDEAがエラーを表示していた)
whenever(mockUserRepository.example(org.mockito.kotlin.any())).thenReturn(11)
mockUserRepository.example(testUser)
verify(mockUserRepository).example(org.mockito.kotlin.any())
こんな感じで、パッケージ名まで明示してやると動くようになった。
参考
-
Mockito-Kotlinのリポジトリ
wikiを見ると基本的な利用方法が書かれている。