Mockitoで複数のmethodのモックを作るときの注意
使用しているJUnitはJUnit4です。
したいこと
例えば、CatRepositoryというclassがNyaoDataSourceというclassに依存しているとします。
この時に
@RunWith(MockitoJUnitRunner::class)
class CatRepository @Inject constructor(
val nyaoDataSource: NyaoDataSource
): CatRepository {
// 指定の猫が鳴けるかどうかを確認する
override suspend fun getChirp(baseCat: BaseCat): CatInfo {
if (nyaoDataSource.checkChirp(baseCat.nameCat) == true) {
return nyaoDataSource.getChirp(baseCat.nameCat)
}
throw IllegalStateException("This cannot chirp...sorry.")
}
}
このように書けるわけですが、このmethodをテストしようとするとnyaoDataSource.checkChirp
とnyaoDataSource.getChirp
の2つをモックにしてテストしなくてはいけません。
したがって、今回したいことは「Mockitoで複数のmethodのモックを適切に作ること」です。
最初に作ったコード(予期せぬ動きをしたコード)
まず、予期しない動きをしたコードです。
...中略
private val mockNyaoLocal = mock(NyaoDataSource::class.java)
@Test
fun getAuthInfoTest() {
runBlocking {
val inputDummy = BaseCat(
nameCat = "Tama",
cate = "mike",
)
mockNyaoLocal.apply {
`when`(checkChirp(inputDummy.nameCat)).thenReturn(false)
`when`(getAccountInfo(inputDummy.nameCat))
.thenThrow(IllegalArgumentException("この猫はいない。いるかどうか、チェックしてから使ってね."))
}
val catRepositoryImpl = CatRepositoryImpl(mockNyaoLocal)
val e: IllegalStateException
= assertThrows(IllegalStateException::class.java){
runBlocking {
catRepositoryImpl.getChirp(inputDummy)
}
}
assertEquals("この猫はいないよ。",
e.message)
}
}
}
この結果、通るはずのテストが通りません。なぜかと言うと、CatRepository.ktの中にあるif文の中の処理も実行されています(実際は実行されているわけではなく、その挙動は以下の通りの推定だと思います)。
ここで、おかしいのはCatRepositoryTest.ktの中にある
mockNyaoLocal.apply {
`when`(checkChirp(inputDummy.nameCat)).thenReturn(false)
`when`(getAccountInfo(inputDummy.nameCat))
.thenThrow(IllegalArgumentException("この猫はいない。いるかどうか、チェックしてから使ってね."))
}
この部分です。whenのどちらかの条件に一致した場合、applyの中すべてが実行されてしまうようです。MockitoのReferenceを見ようと調べたのですが、あまりいいのが見つからず、同時にapplyの挙動について調べた結果[1][2]、なかなか理解できなかったので、私の推定が合っているかどうか、どなたか、ご教授いただけると幸いです。
動いたコード
動いたコードは以下です。
...中略
private val mockNyaoLocal = mock(NyaoDataSource::class.java)
@Test
fun getAuthInfoTest() {
runBlocking {
val inputDummy = BaseCat(
nameCat = "Tama",
cate = "mike",
)
doReturn(false)
.`when`(mockNyaoLocal)
.checkChirp(inputDummy.nameCat)
doReturn(IllegalArgumentException("この猫はいない。いるかどうか、チェックしてから使ってね."))
.`when`(mockNyaoLocal)
.getChirp(inputDummy.nameCat)
val catRepositoryImpl = CatRepositoryImpl(mockNyaoLocal)
val e: IllegalStateException
= assertThrows(IllegalStateException::class.java){
runBlocking {
catRepositoryImpl.getChirp(inputDummy)
}
}
assertEquals("この猫はいないよ。",
e.message)
}
}
}
先程、上手く動いていないコードを
doReturn(false)
.`when`(mockNyaoLocal)
.checkChirp(inputDummy.nameCat)
doReturn(IllegalArgumentException("この猫はいない。いるかどうか、チェックしてから使ってね."))
.`when`(mockNyaoLocal)
.getChirp(inputDummy.nameCat)
と書き換えています。こうすると、独立してそれぞれのwhenが動き、予期した動きが実現出来ます。
ですが、使われないstubということで、コンパイラに怒られますので、今回の場合下のExceptionは要りません。
-
Kotlin スコープ関数 用途まとめ / @ngsw_taro https://qiita.com/ngsw_taro/items/d29e3080d9fc8a38691e (2022-01-16閲覧) ↩︎
-
Kotlin の let(), apply(), run(), with() を使いこなす / Masamichi Yoshii http://extra-vision.blogspot.com/2016/11/kotlin-let-apply-run-with.html ↩︎
Discussion