🍸

Mockitoで書いたテストコードをMockito-Kotlinに書き換える

2021/03/02に公開

※2020/05/26 に書いた記事です。
 個人ブログ閉鎖のため、アクセスがあった記事をZennに転記。

はじめに

Android開発のテスト用のモック作成で
「Mockito」なるライブラリが有用と教えていただきました。

KotlinでJava向けのMockitoもそのまま使えるんですが、
any()を使用すると、java.lang.IllegalStateException: Mockito.any() must not be null  が発生するという問題に遭遇しました。

原因と回避方法はこちらの記事に書かれているのを発見しましたが、
そもそもMockito-Kotlinを使えば問題ないみたいです。
せっかくならMockito-Kotlin使ってみましょう、という事で本記事を書きました。

Mockito-Kotlinってなんぞ

オランダの Niek Haarman さんが作成したKotlin向けのライブラリです。
こちらのWikiのトップでもany()について言及されてます。
※執筆時は個人のリポジトリだったのですが、本家に吸収されたようです

https://github.com/mockito/mockito-kotlin

以下、本家MockitoのWikiから、「Kotlin向けは既存のライブラリがあるからこっちを参照してくれ」とリンク貼られてるので公認です。

https://github.com/mockito/mockito/wiki/Mockito-for-Kotlin

Mockitoで書いたテストコード

// Gradle
dependencies {
    testImplementation "org.mockito:mockito-core:2.28.2"
}

※テスト用にJUnitやAssertJを別途入れてます。

import org.junit.Test
import org.mockito.Mockito.*
import java.util.*
import org.assertj.core.api.Assertions.*

class MockitoTest {
    @Test
    fun `東京と札幌の気温差が正常に計算される事を確認する`() {
        val mock = mock(FooClass::class.java)

        `when`(mock.getTemperature(cityName = "Sapporo")).thenReturn(20)
        `when`(mock.getTemperature(cityName = "Tokyo")).thenReturn(30)

        val bar = BarClass(foo = mock)
        val result = bar.report()
        val expectedResult = 10

        assertThat(result).isEqualTo(expectedResult)
    }
}

(クラス構成は適当です)
BarClassは、気温差を計算するreport()を実装します。
report()内で、FooClassのgetTemperature()を呼んで、指定された都市の気温を取るとします。

ポイントは以下です

  • BarClassをテストするコードなので、呼び出し先のFooClassはmock化します。
  • when thenReturn で「引数が〇〇の時は△△を返す」みたいなgetTemperature()の動きを定義してあげます。
  • BarClassにはFooClassのmockインスタンスを使ってもらいます。
 `when`(mock.getTemperature(cityName = any())).thenReturn(20)

「any()」は、引数ごとに挙動を振り分ける必要がない時に使います。
上のコードは「引数に何入れられても20を返すよ」みたいな感じです。

引数が「ネストしてるデータクラス」とかの場合も「any()」と書いておけば、いちいち仮データを全部準備しなくていいので便利です。
便利なんですが、上述の通り、Kotlinではエラーになるのです。

Mockito-Kotlinに置き換えたテストコード

// Gradle
dependencies {
    testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
}
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.mock
import org.assertj.core.api.Assertions.*
import org.junit.Test

class MockitoKotlinTest {
    @Test
    fun `東京と札幌の気温差が正常に計算される事を確認する`() {
        val mock = mock<FooClass> {
            on { getTemperature(cityName = "Sapporo") } doReturn 20
            on { getTemperature(cityName = "Tokyo") } doReturn 30
        }

        val bar = BarClass(foo = mock)
        val result = bar.report()
        val expectedResult = 10

        assertThat(result).isEqualTo(expectedResult)
    }
}

mock生成〜設定の書き方がガラッと変わります。
on doReturn を使って書きます。

val mock = mock<FooClass> {
    on { getTemperature(cityName = any()) } doReturn 20
}

any()の書き方は超シンプル。

さいごに

超簡単なパターンしか書いてませんが、他に気になることがあればプロジェクトのWikiを参照してみてください。
既存のテストコード資産が膨大なら、置き換える事で得られる恩恵より工数のデメリットの方が大きそうです。

(2020/8/25 追記)
こちらの記事にて、Mockito-Kotlinを採用するメリットについて考察されていました。
「MockK」というまた別のMockライブラリと比較して高速であること、バッククオートを書かなくて良いなどのコーディングのしやすさがメリットとして綴られています。勉強になりますなぁ。
https://blog.nnn.dev/entry/2020/06/11/185929

Discussion