5
3

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 1 year has passed since last update.

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

Posted at

はじめに

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

また、ここでは実際に 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

Spy
出力結果をそのまま外部へ出す
Test Spy.gif

mockk における Mock と Spy の違い

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

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

Spy

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

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 を実際に試していきましょう

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

Example.kt
class Example {

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

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

Mock

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

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

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

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)
    }
}

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

参考資料

おわりに

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

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

5
3
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
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?