追記
mockito 2.23.0 で suspend fun のサポートが入った(
Support mocking kotlin suspend functions compiled by Kotlin 1.3 (#1500))と教えていただいたので、試してみました。
2.23.0 の mockito なら以下のテストが成功しました!やったー!
// このテストが 2.23.0 の mockito なら成功する!
@Test
fun test() {
runBlocking {
val mock = mock(Greeting::class.java).apply {
`when`(hello()).thenReturn("Hello Android")
}
val counter = GreetingTextCounter(mock)
assertThat(counter.count()).isEqualTo(13)
}
}
もともと試した mockito のバージョンは 2.8.9 です。(なぜこのバージョンかというと iosched がこのバージョンを使っていたからです。特に深い意味はありません。)
以下は mockito 2.8.9 での動作です。
以下のような interface と class があるとします。
interface Greeting {
fun hello(): String
}
class GreetingTextCounter(private val greeting: Greeting) {
fun count(): Int {
return greeting.hello().length
}
}
Greeting のモックを用意して特定の文字列を hello() で返すようにすれば、GreetingTextCounter.count() のテストができます。
@Test
fun test() {
val mock = mock(Greeting::class.java).apply {
`when`(hello()).thenReturn("Hello Android")
}
val counter = GreetingTextCounter(mock)
assertThat(counter.count()).isEqualTo(13)
}
ここで Greeting.hello() と GreetingTextCounter.count() を suspend にします。
interface Greeting {
suspend fun hello(): String
}
class GreetingTextCounter(private val greeting: Greeting) {
suspend fun count(): Int {
return greeting.hello().length
}
}
すると、以下のテストは失敗します。
// このテストは失敗する
@Test
fun test() {
runBlocking {
val mock = mock(Greeting::class.java).apply {
`when`(hello()).thenReturn("Hello Android")
}
val counter = GreetingTextCounter(mock)
assertThat(counter.count()).isEqualTo(13)
}
}
`when`().thenReturn() で hello() のときに "Hello Android" を返すように指定していても、GreetingTextCounter.count() のところで greeting.hello() が null を返してしまい、 java.lang.NullPointerException になります。
以下のように Greeting を実装した object を用意して hello() を override すればテストは成功します。
// このテストは成功する
@Test
fun test() {
runBlocking {
val mock = object : Greeting {
override suspend fun hello() = "Hello Android"
}
val counter = GreetingTextCounter(mock)
assertThat(counter.count()).isEqualTo(13)
}
}
しかし使用しないメソッド以外も override しなければいけないのでよくありません。
そこで Delegation を活用し、使用しないメソッドは Mockito の mock に流すようにします。
// このテストは成功する
@Test
fun test() {
runBlocking {
val mock = object : Greeting by mock(Greeting::class.java) {
override suspend fun hello() = "Hello Android"
}
val counter = GreetingTextCounter(mock)
assertThat(counter.count()).isEqualTo(13)
}
}
これで必要なメソッドだけ override し、テストも通るようになります。