mockito-kotlin を使って Suspending 関数を含むクラスのテストを書いていたところ、現状ではちょっとしたコツが必要なことが分かった。
以下の DownloadService#downloadFile()
を呼び出す別のメソッドをテストするため、このインターネットと通信する downloadFile()
メソッドをモックに置き換えようとした。
mock()
を使った以下のコードは正常に実行可能。
class DownloadService {
suspend fun downloadFile(url: String): File {
// 省略
}
// 実際には別のメソッドも定義されている
}
@Test fun testDownloadFile() {
val file = createTempFile()
val target = mock<DownloadService>() {
onBlocking{ downloadFile(any()) } doReturn file
}
runBlocking {
val downloadedFile = target.downloadFile("http://example.com")
assertEquals(file, downloadedFile)
}
}
実際には DownloadService
クラスに含まれる downloadFile()
メソッド以外はモックを使わずにそのまま実行されて欲しかったので、mock()
を spy()
に置き換え、このメソッドだけをモック実装する形に。
@Test fun testDownloadFile() {
val file = createTempFile()
val target = spy<DownloadService>() {
onBlocking{ downloadFile(any()) } doReturn file
}
runBlocking {
val downloadedFile = target.downloadFile("http://example.com")
assertEquals(file, downloadedFile)
}
}
ところが、このテストコードを実行すると NullPointerException
がスローされてしまう(4 行目で実行される Stub 処理中に)。
mock()
はいいけど spy()
だとダメ。
しかしながら、このように spy()
を使ってもモック対象(今回の例で言うと downloadFile()
メソッド)が引数を取らない場合は、NPE がスローされることなくテストを実行できることが分かった。
試行錯誤した結果、spy()
にラムダ式でモック処理を渡すのではなく、次のように doReturn().whenever().xxxx()
のコードでモック処理を書けば正常にテストが実行できるようになった。
@Test fun testDownloadFile() = runBlocking {
val file = createTempFile()
val target = spy<DownloadService>()
doReturn(file).whenever(target).downloadFile(any())
val downloadedFile = target.downloadFile("http://example.com")
assertEquals(file, downloadedFile)
}