はじめに
よく「モックする」というものの「モック」の意味がモノによって異なることを学んだので、それらを備忘録としてまとめていこうと思います。
また、ここでは実際に 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 内部でその結果と期待値を検証し、検証結果を外部に出す
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をそれぞれ返すメソッドだけを定義しています。
class Example {
fun getA(): String {
return "A"
}
fun getB(): String {
return "B"
}
}
Mock
メソッドの戻り値をテストコード上でセットしないといけません。
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
元々の本物のメソッドをそのまま呼び出すことが出来ます。
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
- mockk
- 自動テストでモックするって、なにそれ?おいしいの?/what_is_mocking
おわりに
Spy を何の気なしに使用すると恐ろしいことになりそうだなと感じました…。また、Mock と Spy を適切に使い分けしていきたいところです。
ここまで読んでいただき、ありがとうございました!何かありましたら、コメントをお願いいたします。