[Mockito] ReflectionでKotlinのprotectedなメソッドをモックする

# はじめに

この記事では[Mockito](https://site.mockito.org/)を使って、`protected`なメソッドをモックする方法について解説します。

今回はよく使われていると思われる、Template Methodパターンでの`protected`なメソッドのモックをサンプルとして、解説を進めていきたいと思います。

ソースコード : [shiita0903/ProtectedMock](https://github.com/shiita0903/ProtectedMock/tree/master/src/main/kotlin/jp/shiita/protectedmock) 

# ライブラリ

- JUnit 4.12
- AssertJ 3.11.1
- Mockito 3.0.0
- Mockito-Kotlin 2.1.0

# テストの前に

Kotlinではデフォルトが`final class`なので、そのままではMockitoを使ってモック出来ません。[MockitoのWiki]([https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2#unmockable](https://github.com/mockito/mockito/wiki/What's-new-in-Mockito-2#unmockable))で書かれているように、MockMakerを用意する必要があります。

# テスト対象のプログラム
## ViewModel
MVVMアーキテクチャのViewModelがテスト対象で、ViewModelで利用しているUseCaseをモックしてテストをします。

```kotlin
class ViewModel(
    private val id: Int,
    private val concreteUseCase: ConcreteUseCase
) {
    fun loadData(): String {
        val resultData = concreteUseCase(id)
        return "${resultData.data1}:${resultData.data2}"
    }
}
```
  
## UseCase
UseCaseではTemplate Methodパターンで処理の共通化をしていて、`execute()`メソッドを具象クラスで実装します。[Google IOのソース](https://github.com/google/iosched)とかが参考になると思います。

`execute()`メソッドは`protected`なメソッドなので、そのままではモックすることが出来ません。

```kotlin
data class ResultData(val data1: Int, val data2: String)

class ConcreteUseCase(
    private val repository1: Repository1,
    private val repository2: Repository2
) : UseCase<Int, ResultData>() {
  
    // protectedなのでモック出来ない
    override fun execute(parameters: Int): ResultData {
        val data1 = repository1.loadData(parameters)
        val data2 = repository2.loadData(parameters)
        return ResultData(data1, data2)
    }
}

abstract class UseCase<in P, R> {

  	protected abstract fun execute(parameters: P): R

    operator fun invoke(parameters: P): R {
        // ローディング等の共通化処理
        return execute(parameters)
    }
}
```


## Repository
Repositoryではデータのロード等をするとします。この部分の処理はViewModelのテストと関係ないので、適当に値を返す実装です。

```kotlin
class Repository1 {
    fun loadData(id: Int): Int = id
}

class Repository2 {
    fun loadData(id: Int): String = "$id"
}
```



まとめると、ViewModel -> UseCase (-> Repository)のような依存関係がある中で、ViewModelのテストのために、UseCaseの`protected`なメソッドをモックしたいといった感じです。

# テストコード

まず`ConcreteUseCase`クラスのSpyを作成します。`invoke()`メソッドの内部で`execute()`メソッドが呼ばれるように、ここではMockではなくSpyを利用します。Repositoryのインスタンスが必要なので適当にモックしておきます。

```kotlin
val useCase = spy(ConcreteUseCase(mock(), mock()))
```
  

`ConcreteUseCase`の`execute()`メソッドをモックします。`execute()`メソッドは`protected`なので以下のコードはコンパイルエラーになってしまいます。この問題は後ほど解決するとして、テストコードを読み進めていきます。

```kotlin
doReturn(ResultData(100, "hoge"))
            .whenever(useCase)
            .execute(any())
```



`ConcreteUseCase`クラスのSpyを使って`ViewModel`クラスのインスタンスを作成します。`loadData()`メソッドの戻り値のアサーションと、`ConcreteUseCase`クラスの`invoke()`メソッドが正しく呼ばれているかをテストします。

```kotlin
val viewModel = ViewModel(id, useCase)

val actual = viewModel.loadData()

assertThat(actual).isEqualTo("100:hoge")
verify(useCase, times(1)).invoke(id)
```



テストコードの全体はこのようになっています。

```kotlin
class ViewModelTest {
    @Test
    fun loadData() {
        val id = 10
        // invokeからexecuteが呼び出されるようにするため、mockではなくspyを使う
        val useCase = spy(ConcreteUseCase(mock(), mock()))
        doReturn(ResultData(100, "hoge"))
            .whenever(useCase)
            .execute(any())    // ここでコンパイルエラー
        val viewModel = ViewModel(id, useCase)

        val actual = viewModel.loadData()

        assertThat(actual).isEqualTo("100:hoge")
        verify(useCase, times(1)).invoke(id)
    }
}
```

# protectedなメソッドのモック

テストコードでコンパイルエラーとなってしまった、`protected`なメソッドのモックをリフレクションで実現します。`declaredMemberFunctions`からモック対象の`execute()`メソッドを取得し、`isAccessible = true`でアクセス可能にしてから`call()`メソッドで呼び出します。 

```kotlin
// protectedなメソッドの代わりに使う拡張関数
fun <P, R> UseCase<P, R>.execute(parameters: P) =
    this::class.declaredMemberFunctions.single { it.name == "execute" }.let { function ->
        function.isAccessible = true
        function.call(this, parameters)
    }
```



ここでのポイントは、スーパクラスの`UseCase`に`protected`なメソッドと同じ名前の`execute()`拡張関数を定義しているところです。このようにリフレクションのコードを書くと、テストコードからは、`protected`なメソッドを直接モックできているように記述できます。

これでコンパイルエラーの部分は、問題なく実行出来るようになっているはずです。

# おわりに

Template Methodパターンはよく使うパターンだと思うので、`protected`なメソッドのモックは結構需要あるんじゃないかと思ってます。UseCaseでなくても、`protected`なメソッドと同名のリフレクションを利用した拡張関数を書くことで、快適にテストを書けるようになるはずです。