4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Kotlinの単体テストでトップレベルの関数をMockitoでMockにする方法

Last updated at Posted at 2018-04-15

概要

Kotlinではクラスに属さないメソッドを定義できます。
例えば以下です。

DateExtension.kt
package sample.ko2ic

fun jstCalendar() = Calendar.getInstance().apply {
    timeZone = TimeZone.getTimeZone("Asia/Tokyo")
}

で、以下のコードのようにjstCalendar()を利用している箇所の単体テストをするときにどのようにjstCalendar()をモックにするかという話です。

class Hoge {
    fun day() :Int {
        val calendar = jstCalendar()
        return calendar.get(Calendar.DAY_OF_MONTH)
    }
}

なお、Mockito + PowerMockでmock化しようとしてもできません。
Javaクラスになった時にstaticメソッドになるのでPowerMockでできそうですが、生成されるクラス名がわからないのでできないのです。
拡張関数に関しても同様です。

方法

Mockitoでテストするとしたら、2018年4月現在、インターフェイスを変えないようにデフォルト引数を利用するしかないと思われます。(後述しますが、Mockkを使えばできます。)

  • まずは、Topレベルの関数用にインターフェイスとその実装を追加し、それをデフォルト引数で渡すようにします。
DateExtension.kt
package sample.ko2ic

interface DateExtension {
    fun DateExtension.jstCalendar(): Calendar
}

class DateExtensionImpl : DateExtension {
    override fun DateExtension.jstCalendar() = Calendar.getInstance().apply {
        timeZone = TimeZone.getTimeZone("Asia/Tokyo")
    }
}

fun jstCalendar(implement: DateExtension = DateExtensionImpl()): Calendar {
    val ext = fun DateExtension.(): Calendar = jstCalendar()
    return ext.invoke(implement)
}

ちなみにfun DateExtension.()レシーバ付き関数リテラルという記法です。
DateExtensionがレシーバーで戻り値がCalendar型の関数リテラルです。
このとき呼び出されているjstCalendar()DateExtensionインターフェイスのメソッドです。

以下のようにも書けます。DateExtensionにClosure(引数がなく戻り値がCalendar)を返す拡張関数を定義しています。

    val ext: DateExtension.() -> Calendar = {
        jstCalendar()
    }
  • 次にjstCalendar()と利用している箇所をテストしやすいように少し変更します。(引数にデフォルト実装をすることでインターフェイスが変わらないようにする)
class Hoge {
    fun day(implement: DateExtension = DateExtensionImpl()) :Int {
       val ext = fun DateExtension.(): Calendar = jstCalendar()
       val calendar = ext.invoke(implement)
       return calendar.get(Calendar.DAY_OF_MONTH)
    }
}
  • 後は単純に単体テストで実装を変えるだけです。
    @Test
    fun day(){
        val now = object : DateExtension {
            override fun DateExtension.jstCalendar(): Calendar {
                val now = Calendar.getInstance().apply {
                    clear()
                    set(2000,Calendar.JANUARY,1)
                }
                return now
            }
        }

        val target = Domain()
        val actual = target.day(now)

        Assert.assertEquals(1, actual)
    }

Mockk

色々書いたけど、結局Mockkを使えば、実装ファイルを何も変えないで単体テストをできるので便利です。
文字列でクラスを指定できます。

    @Test
    fun day() {
        val target = Domain()

        val now = Calendar.getInstance().apply {
            clear()
            set(2000,Calendar.JANUARY,1)
        }

        staticMockk("sample.ko2ic.mockk.DateExtensionKt").use {
            every {
                jstCalendar()
            } returns now

            assertEquals(1, target.day())

            verify {
                jstCalendar()
            }
        }
    }

結論

  • Kotlin + Mockitoは、拡張関数やトップレベルの関数の単体テストがしづらくなるので気をつけよう。
  • Mockkは便利だね。
4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?