【Kotlin】Mock と Spy の違いについて（mockk で試してみた）

## はじめに

よく「モックする」というものの「モック」の意味がモノによって異なることを学んだので、それらを備忘録としてまとめていこうと思います。

また、ここでは実際に mockk における Mock と Spy の違いを確認していきます。（※mockk とは Kotlin のモックライブラリです）

### この記事で分かること

- 言葉の定義
    - xUnit Test Patterns における Mock と Spy の違い
    - mockk における Mock と Spy の違い
- mockk で動作を確認
    - Mock
    - Spy

## 言葉の定義

まず大前提として、モックやスパイの意味は文脈や人によっても異なる可能性があるので注意が必要です。それを踏まえた上で、本記事では「xUnit Test Patterns」と「mockk」のそれぞれにおける言葉の定義を、自分なりにかなり簡潔にまとめていきます。（詳しくは最後の参考資料をご覧ください）

### xUnit Test Patterns における Mock と Spy の違い

- 共通点
    - 間接出力を扱える
- 相違点
    - そのオブジェクト内部で検証まで行うか否か

**Mock**
期待値を Mock に渡して、テスト対象から呼び出された時に Mock 内部でその結果と期待値を検証し、検証結果を外部に出す
![Mock Object.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1630025/d6f02537-3e9f-dea9-1e12-d77394722c1a.gif)


**Spy**
出力結果をそのまま外部へ出す
![Test Spy.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1630025/d7dd6fc5-d945-260e-5362-46f2a39838ad.gif)


### mockk における Mock と Spy の違い

- 共通点
    - 呼び出されたかどうかを検証できる
- 相違点
    - 元々のメソッドを呼び出すか否か

**Mock**
メソッドを呼び出す場合、そのメソッドは必ずニセモノになる

**Spy**

元々の本物のメソッドをそのまま呼び出す可能性がある
（テストコード上で上書きすることは可能）

```kotlin
val car = spyk(Car()) // or spyk<Car>() to call the default constructor

car.drive(Direction.NORTH) // returns whatever the real function of Car returns

verify { car.drive(Direction.NORTH) }

confirmVerified(car)
```

## mockk で動作を確認

それでは [mockk](https://mockk.io/) を実際に試していきましょう

ここではテスト対象のコードとして `Example` クラスを用意しました。ただ単純にAとBをそれぞれ返すメソッドだけを定義しています。

```kotlin:Example.kt
class Example {

    fun getA(): String {
        return "A"
    }

    fun getB(): String {
        return "B"
    }
}

```

### Mock
メソッドの戻り値をテストコード上でセットしないといけません。

``` kotlin:MockExampleUnitTest.kt
class MockExampleUnitTest {
    @get:Rule
    val mockkRule = MockKRule(this)

    @MockK
    lateinit var mockExample: Example

    @Test
    fun mock_test_1() {
        // Mockの場合、このまま呼び出すとエラーが出る
        val a = mockExample.getA()
        val b = mockExample.getB()

        assertEquals("A", a)
        assertEquals("B", b)

        // ちゃんと1回ずつ呼び出されているか確認
        verify(exactly = 1) { mockExample.getA() }
        verify(exactly = 1) { mockExample.getB() }

        // 全ての呼び出しが verify で検証されたか確認
        confirmVerified(mockExample)
    }

    @Test
    fun mock_test_2() {
        // メソッドを呼び出してどんな結果が返るかをここでセットしないといけない
        every { mockExample.getA() } returns "A"
        every { mockExample.getB() } returns "BBB" // 好きなように上書き可能

        val a = mockExample.getA()
        val b = mockExample.getB()

        assertEquals("A", a)
        assertNotEquals("B", b)
        assertEquals("BBB", b)

        // ちゃんと1回ずつ呼び出されているか確認
        verify(exactly = 1) { mockExample.getA() }
        verify(exactly = 1) { mockExample.getB() }

        // 全ての呼び出しが verify で検証されたか確認
        confirmVerified(mockExample)
    }
}
```

そのまま呼び出した `mock_test_1` を実行すると以下のようなエラーが起こり、テストすることが出来ないため、注意が必要です。
```
no answer found for: Example(mockExample#1).getA()
io.mockk.MockKException: no answer found for: Example(mockExample#1).getA()
```

### Spy

元々の本物のメソッドをそのまま呼び出すことが出来ます。

```kotlin:SpyExample.kt
class SpyExampleUnitTest {
    @get:Rule
    val mockkRule = MockKRule(this)

    // Spy の時は lateinit では無いので注意
    @SpyK
    var spyExample = Example()

    @Test
    fun spy_test_1() {
        // Spy の場合、このまま呼び出すことが可能(元々のメソッドをそのまま呼び出している)
        val a = spyExample.getA()
        val b = spyExample.getB()

        assertEquals("A", a)
        assertEquals("B", b)

        // ちゃんと1回ずつ呼び出されているか確認
        verify(exactly = 1) { spyExample.getA() }
        verify(exactly = 1) { spyExample.getB() }

        // 全ての呼び出しが verify で検証されたか確認
        confirmVerified(spyExample)
    }

    @Test
    fun spy_test_2() {
        // テストコード上で上書きすることも可能
        // ここでは getB だけを書き換えて、それ以外は本物のメソッドをそのまま呼び出しています
        every { spyExample.getB() } returns "BBB"

        val a = spyExample.getA()
        val b = spyExample.getB()

        assertEquals("A", a)
        assertNotEquals("B", b)
        assertEquals("BBB", b)

        // ちゃんと1回ずつ呼び出されているか確認
        verify(exactly = 1) { spyExample.getA() }
        verify(exactly = 1) { spyExample.getB() }

        // 全ての呼び出しが verify で検証されたか確認
        confirmVerified(spyExample)
    }
}

```

上書きをしない場合、本物のメソッドをそのまま呼び出すため、使用する場合には気を付ける必要がありそうです。

## 参考資料

- xUnit Test Patterns
    - [Mock Object](http://xunitpatterns.com/Mock%20Object.html)
    - [Test Spy](http://xunitpatterns.com/Test%20Spy.html)
- [mockk](https://mockk.io/)
- [ 自動テストでモックするって、なにそれ？おいしいの？/what_is_mocking](https://speakerdeck.com/marchin1989/what-is-mocking?slide=30)


## おわりに

Spy を何の気なしに使用すると恐ろしいことになりそうだなと感じました…。また、Mock と Spy を適切に使い分けしていきたいところです。

ここまで読んでいただき、ありがとうございました！何かありましたら、コメントをお願いいたします。