MockK:Kotlinのモッキングライブラリ

  • Kotlin

  • link:/category/testing/ [テスト]

1.  概要

このチュートリアルでは、https://mockk.io/ [MockK]ライブラリの基本機能のいくつかを見ていきます。

*2. MockK *

link:/kotlin[Kotlin]では、すべてのクラスとメソッドがfinalです。 これは不変のコードを書くのに役立ちますが、テスト中にいくつかの問題も引き起こします。
ほとんどのJVMモックライブラリには、finalクラスのモッキングまたはスタブ化に関する問題があります。 もちろん、モックしたいクラスとメソッドに「_open_」キーワードを追加できます。 ただし、一部のコードをモックするためだけにクラスを変更することは、最善のアプローチではありません。
ここに、MockKライブラリがあります。これは、Kotlin言語の機能と構成のサポートを提供します。 MockKは、模擬クラスのプロキシを構築します。 これによりパフォーマンスが多少低下しますが、MockKが提供する全体的なメリットは価値があります。

3.  インストール

インストールはできる限り簡単です。 Mavenプロジェクトにhttps://search.maven.org/search?q=g:io.mockk%20a:mockk[mockk]依存関係を追加するだけです。
<dependency>
    <groupId>io.mockk</groupId>
    <artifactId>mockk</artifactId>
    <version>1.9.3</version>
    <scope>test</scope>
</dependency>
link:/gradle[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

  • 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)
}
この例では、_relaxed_属性とともに_mockk_メソッドを使用して、リラックスしたモックオブジェクトを作成しました。 _ @ 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
}
_Foo_クラスには、__ Bar型のフィールドが含まれています。 __これで、1つの簡単なステップで構造をモックできます。 _name_および_nickname_フィールドをモックしましょう:
@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。 パラメータのキャプチャ*

メソッドに渡されたパラメータをキャプチャする必要がある場合は、_CapturingSlot_または_MutableListを使用できます。 _CapturingSlot:_の例を次に示します。
@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の詳細については、https://mockk.io/ [MockK Webサイトのドキュメント]を確認できます。
いつものように、提示されたサンプルコードはhttps://github.com/eugenp/tutorials/tree/master/kotlin-libraries[GitHubで]から入手できます。