MockK:Kotlinのモッキングライブラリ
1. 概要
このチュートリアルでは、MockKライブラリの基本的な機能のいくつかを見ていきます。
2. MockK
Kotlin では、すべてのクラスとメソッドがfinalです。 これは不変のコードを書くのに役立ちますが、テスト中にいくつかの問題を引き起こします。
ほとんどのJVMモックライブラリには、最終クラスのモックまたはスタブに関する問題があります。 もちろん、モックしたいクラスやメソッドに「open」キーワードを追加することもできます。 しかし、一部のコードをモックするためだけにクラスを変更することは、最善のアプローチとは思えません。
Kotlin言語の機能と構成をサポートするMockKライブラリが登場します。 MockKは、モックされたクラスのプロキシを構築します。 これによりパフォーマンスがいくらか低下しますが、MockKがもたらす全体的なメリットはそれだけの価値があります。
3. インストール
インストールは可能な限り簡単です。 mockk依存関係をMavenプロジェクトに追加する必要があります。
<dependency>
<groupId>io.mockk</groupId>
<artifactId>mockk</artifactId>
<version>1.9.3</version>
<scope>test</scope>
</dependency>
Gradle の場合、テスト依存関係として追加する必要があります。
testImplementation "io.mockk:mockk:1.9.3"
4. 基本例
モックしたいサービスを作成しましょう。
class TestableService {
fun getDataFromDb(testParameter: String): String {
// query database and return matching value
}
fun doSomethingElse(testParameter: String): String {
return "I don't want to!"
}
}
TestableServiceをモックするテストの例を次に示します。
@Test
fun givenServiceMock_whenCallingMockedMethod_thenCorrectlyVerified() {
// given
val service = mockk<TestableService>()
every { service.getDataFromDb("Expected Param") } returns "Expected Output"
// when
val result = service.getDataFromDb("Expected Param")
// then
verify { service.getDataFromDb("Expected Param") }
assertEquals("Expected Output", result)
}
モックオブジェクトを定義するために、 mockk <…>() 方法。
次のステップでは、モックの動作を定義しました。 この目的のために、どの呼び出しに対してどの応答が返されるかを記述するeveryブロックを作成しました。
最後に、 verify ブロックを使用して、モックが期待どおりに呼び出されたかどうかを確認しました。
5. 注釈の例
MockKアノテーションを使用して、あらゆる種類のモックを作成することができます。 TestableServiceの2つのインスタンスを必要とするサービスを作成しましょう。
class InjectTestService {
lateinit var service1: TestableService
lateinit var service2: TestableService
fun invokeService1(): String {
return service1.getDataFromDb("Test Param")
}
}
InjectTestService には、同じタイプの2つのフィールドが含まれています。 MockKにとっては問題にはなりません。 MockKは、プロパティを名前で照合し、次にクラスまたはスーパークラスで照合しようとします。 また、は、プライベートフィールドへのオブジェクトの挿入に問題はありません。
アノテーションを使用して、テストでInjectTestServiceをモックしてみましょう。
class AnnotationMockKUnitTest {
@MockK
lateinit var service1: TestableService
@MockK
lateinit var service2: TestableService
@InjectMockKs
var objectUnderTest = InjectTestService()
@BeforeEach
fun setUp() = MockKAnnotations.init(this)
// Tests here
...
}
上記の例では、 @InjectMockKs annotation 。を使用しました。これは、定義されたモックが注入されるオブジェクトを指定します。 デフォルトでは、まだ割り当てられていない変数を挿入します。 @OverrideMockKs を使用して、すでに値を持つフィールドをオーバーライドできます。
MockKでは、アノテーション付きの変数を宣言するオブジェクトでMockKAnnotations.init(…)を呼び出す必要があります。 Junit5 の場合、 @ExtendWith(MockKExtension :: class )。
6. スパイ
Spyでは、特定のクラスの特定の部分のみをモックできます。たとえば、 TestableService:の特定のメソッドをモックするために使用できます。
@Test
fun givenServiceSpy_whenMockingOnlyOneMethod_thenOtherMethodsShouldBehaveAsOriginalObject() {
// given
val service = spyk<TestableService>()
every { service.getDataFromDb(any()) } returns "Mocked Output"
// when checking mocked method
val firstResult = service.getDataFromDb("Any Param")
// then
assertEquals("Mocked Output", firstResult)
// when checking not mocked method
val secondResult = service.doSomethingElse("Any Param")
// then
assertEquals("I don't want to!", secondResult)
}
この例では、spykメソッドを使用してスパイオブジェクトを作成しました。 @SpyK アノテーションを使用して、同じことを実現することもできます。
class SpyKUnitTest {
@SpyK
lateinit var service: TestableService
// Tests here
}
7. リラックスしたモック
戻り値が指定されていないメソッドを呼び出そうとすると、一般的なモックオブジェクトはMockKExceptionをスローします。
各メソッドの動作を説明したくない場合は、リラックスしたモックを使用できます。この種類のモックは、各関数のデフォルト値を提供します。 たとえば、 String 戻りタイプは、空のStringを返します。 簡単な例を次に示します。
@Test
fun givenRelaxedMock_whenCallingNotMockedMethod_thenReturnDefaultValue() {
// given
val service = mockk<TestableService>(relaxed = true)
// when
val result = service.getDataFromDb("Any Param")
// then
assertEquals("", result)
}
この例では、mockkメソッドとrelaxed属性を使用して、リラックスしたモックオブジェクトを作成しました。 @RelaxedMockKアノテーションを使用することもできます。
class RelaxedMockKUnitTest {
@RelaxedMockK
lateinit var service: TestableService
// Tests here
}
8. オブジェクトモック
Kotlinは、objectキーワードを使用してシングルトンを宣言する簡単な方法を提供します。
object TestableService {
fun getDataFromDb(testParameter: String): String {
// query database and return matching value
}
}
ただし、ほとんどのモックライブラリには、Kotlinシングルトンインスタンスのモックに問題があります。 このため、MockKはmockkObjectメソッドを提供します。 見てみましょう:
@Test
fun givenObject_whenMockingIt_thenMockedMethodShouldReturnProperValue(){
// given
mockkObject(TestableService)
// when calling not mocked method
val firstResult = service.getDataFromDb("Any Param")
// then return real response
assertEquals(/* DB result */, firstResult)
// when calling mocked method
every { service.getDataFromDb(any()) } returns "Mocked Output"
val secondResult = service.getDataFromDb("Any Param")
// then return mocked response
assertEquals("Mocked Output", secondResult)
}
9. 階層的モッキング
MockKのもう1つの便利な機能は、階層オブジェクトをモックする機能です。 まず、階層オブジェクト構造を作成しましょう。
class Foo {
lateinit var name: String
lateinit var bar: Bar
}
class Bar {
lateinit var nickname: String
}
The フークラスにはタイプのフィールドが含まれています
@Test
fun givenHierarchicalClass_whenMockingIt_thenReturnProperValue() {
// given
val foo = mockk<Foo> {
every { name } returns "Karol"
every { bar } returns mockk {
every { nickname } returns "Tomato"
}
}
// when
val name = foo.name
val nickname = foo.bar.nickname
// then
assertEquals("Karol", name)
assertEquals("Tomato", nickname)
}
10. パラメータのキャプチャ
メソッドに渡されたパラメーターをキャプチャする必要がある場合は、CaptureSlotまたはMutableListを使用できます。
@Test
fun givenMock_whenCapturingParamValue_thenProperValueShouldBeCaptured() {
// given
val service = mockk<TestableService>()
val slot = slot<String>()
every { service.getDataFromDb(capture(slot)) } returns "Expected Output"
// when
service.getDataFromDb("Expected Param")
// then
assertEquals("Expected Param", slot.captured)
}
MutableList を使用して、すべてのメソッド呼び出しの特定の引数値をキャプチャして保存できます。
@Test
fun givenMock_whenCapturingParamsValues_thenProperValuesShouldBeCaptured() {
// given
val service = mockk<TestableService>()
val list = mutableListOf<String>()
every { service.getDataFromDb(capture(list)) } returns "Expected Output"
// when
service.getDataFromDb("Expected Param 1")
service.getDataFromDb("Expected Param 2")
// then
assertEquals(2, list.size)
assertEquals("Expected Param 1", list[0])
assertEquals("Expected Param 2", list[1])
}
11. 結論
この記事では、MockKの最も重要な機能について説明しました。 MockKは、Kotlin言語用の強力なライブラリであり、多くの便利な機能を提供します。 MockKの詳細については、MockKWebサイトのドキュメントを確認してください。
いつものように、提示されたサンプルコードは、GitHubので入手できます。