NT Basement

NT Basement

開発プロダクトの紹介・技術情報の発信・その他思った事を書きます

NT Basement

開発プロダクトの紹介・技術情報の発信

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

f:id:nemotea:20200526173624j:plain

みなさん、自動テストはお好きですか?
私は新卒1年目の時に”手動テスト&エクセルスクショエビデンス”という悪習の洗礼を受けました。
そのあと、UnitTestからUIテスト、速度・メモリ監視などの非機能テストまでゴリゴリにCIでぶん回すチームを両方経験しました。
今は自動テスト大好きです。

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

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

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

<目次>

Mockito-Kotlinってなんぞ

オランダの Niek Haarman さんが作成したKotlin向けのライブラリです。
こちらのWikiのトップでもany()について言及されてます。

github.com

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

github.com

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化します。
  • whenthenReturn で「引数が〇〇の時は△△を返す」みたいな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生成〜設定の書き方がガラッと変わります。
ondoReturn を使って書きます。

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

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

さいごに

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