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


# 概要

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.()```は[レシーバ付き関数リテラル](https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/lambdas.html#%E3%83%AC%E3%82%B7%E3%83%BC%E3%83%90%E4%BB%98%E3%81%8D%E9%96%A2%E6%95%B0%E3%83%AA%E3%83%86%E3%83%A9%E3%83%AB)という記法です。
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](https://github.com/oleksiyp/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は便利だね。