MockK: Library Mocking untuk Kotlin

1. Ikhtisar

Dalam tutorial ini, kita akan melihat beberapa fitur dasar pustaka MockK.

2. MockK

Di Kotlin, semua kelas dan metode bersifat final. Meskipun ini membantu kami menulis kode yang tidak dapat diubah, ini juga menyebabkan beberapa masalah selama pengujian.

Sebagian besar pustaka tiruan JVM memiliki masalah dengan kelas akhir yang mengejek atau menghentikan. Tentu saja, kita dapat menambahkan kata kunci " terbuka " ke kelas dan metode yang ingin kita tiru. Tetapi mengubah kelas hanya untuk mengejek beberapa kode bukanlah pendekatan terbaik.

Inilah perpustakaan MockK, yang menawarkan dukungan untuk fitur dan konstruksi bahasa Kotlin. MockK membangun proxy untuk kelas yang diolok-olok. Hal ini menyebabkan beberapa penurunan kinerja, tetapi manfaat keseluruhan yang diberikan MockK kepada kami sangat berharga.

3. Instalasi

Pemasangannya sesederhana mungkin. Kami hanya perlu menambahkan dependensi mockk ke proyek Maven kami:

 io.mockk mockk 1.9.3 test 

Untuk Gradle, kita perlu menambahkannya sebagai dependensi pengujian:

testImplementation "io.mockk:mockk:1.9.3"

4. Contoh Dasar

Mari buat layanan yang ingin kita tiru:

class TestableService { fun getDataFromDb(testParameter: String): String { // query database and return matching value } fun doSomethingElse(testParameter: String): String { return "I don't want to!" } }

Berikut adalah contoh pengujian yang mengejek TestableService :

@Test fun givenServiceMock_whenCallingMockedMethod_thenCorrectlyVerified() { // given val service = mockk() 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) }

Untuk menentukan objek tiruan, kami telah menggunakan metode mockk () .

Pada langkah berikutnya, kami mendefinisikan perilaku tiruan kami. Untuk tujuan ini, kami telah membuat setiap blok yang menjelaskan respons apa yang harus dikembalikan untuk panggilan mana.

Akhirnya, kami menggunakan verifikasi blok untuk memverifikasi apakah tiruan itu dipanggil seperti yang kita harapkan .

5. Contoh Anotasi

Anda dapat menggunakan anotasi MockK untuk membuat semua jenis tiruan. Mari buat layanan yang membutuhkan dua contoh TestableService kita :

class InjectTestService { lateinit var service1: TestableService lateinit var service2: TestableService fun invokeService1(): String { return service1.getDataFromDb("Test Param") } }

InjectTestService berisi dua kolom dengan tipe yang sama. Ini tidak akan menjadi masalah bagi MockK. MockK mencoba mencocokkan properti dengan nama, lalu dengan kelas atau superclass. Ini juga tidak memiliki masalah dengan memasukkan objek ke dalam bidang pribadi .

Mari kita tiru InjectTestService dalam pengujian dengan menggunakan anotasi:

class AnnotationMockKUnitTest { @MockK lateinit var service1: TestableService @MockK lateinit var service2: TestableService @InjectMockKs var objectUnderTest = InjectTestService() @BeforeEach fun setUp() = MockKAnnotations.init(this) // Tests here ... }

Dalam contoh di atas, kami telah menggunakan anotasi @InjectMockKs . Ini menentukan objek di mana tiruan yang ditentukan harus dimasukkan. Secara default, ini memasukkan variabel yang belum ditetapkan. Kita bisa menggunakan @OverrideMockKs untuk mengganti bidang yang sudah memiliki nilai.

MockK membutuhkan MockKAnnotations.init (…) untuk dipanggil pada objek yang mendeklarasikan variabel dengan penjelasan. Untuk Junit5, itu bisa diganti dengan @ExtendWith (MockKExtension :: class) .

6. Mata-mata

Spy memungkinkan hanya mengejek bagian tertentu dari beberapa kelas. Misalnya, ini dapat digunakan untuk meniru metode tertentu di TestableService:

@Test fun givenServiceSpy_whenMockingOnlyOneMethod_thenOtherMethodsShouldBehaveAsOriginalObject() { // given val service = spyk() 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) }

Dalam contoh, kami telah menggunakan metode spyk untuk membuat objek mata-mata. Kita juga bisa menggunakan anotasi @SpyK untuk mencapai hal yang sama:

class SpyKUnitTest { @SpyK lateinit var service: TestableService // Tests here }

7. Santai Mock

Objek tipikal tiruan akan menampilkan MockKException jika kita mencoba memanggil metode yang nilai kembaliannya belum ditentukan.

Jika kita tidak ingin mendeskripsikan perilaku masing-masing metode, maka kita dapat menggunakan ejekan yang santai. Jenis tiruan ini memberikan nilai default untuk setiap fungsi. Misalnya, tipe kembali String akan mengembalikan String kosong . Berikut contoh singkatnya:

@Test fun givenRelaxedMock_whenCallingNotMockedMethod_thenReturnDefaultValue() { // given val service = mockk(relaxed = true) // when val result = service.getDataFromDb("Any Param") // then assertEquals("", result) }

Dalam contoh, kami telah menggunakan metode mockk dengan atribut santai untuk membuat objek tiruan santai. Kami juga dapat menggunakan anotasi @RelaxedMockK :

class RelaxedMockKUnitTest { @RelaxedMockK lateinit var service: TestableService // Tests here }

8. Objek Mock

Kotlin menyediakan cara mudah untuk mendeklarasikan singleton dengan menggunakan kata kunci object :

object TestableService { fun getDataFromDb(testParameter: String): String { // query database and return matching value } }

Namun, sebagian besar library tiruan memiliki masalah dengan instance tunggal Kotlin yang meniru. Karena itu, MockK menyediakan metode mockkObject . Mari lihat:

@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. Mengolok-olok Hierarki

Fitur berguna lainnya dari MockK adalah kemampuan untuk meniru objek hierarki. Pertama, mari buat struktur objek hierarki:

class Foo { lateinit var name: String lateinit var bar: Bar } class Bar { lateinit var nickname: String }

The Foo class contains a field of type Bar. Now, we can mock the structure in just one easy step. Let's mock the name and nickname fields:

@Test fun givenHierarchicalClass_whenMockingIt_thenReturnProperValue() { // given val foo = mockk { 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. Capturing Parameters

If we need to capture the parameters passed to a method, then we can use CapturingSlot or MutableList. It is useful when we want to have some custom logic in an answer block or we just need to verify the value of the arguments passed. Here is an example of CapturingSlot:

@Test fun givenMock_whenCapturingParamValue_thenProperValueShouldBeCaptured() { // given val service = mockk() val slot = slot() every { service.getDataFromDb(capture(slot)) } returns "Expected Output" // when service.getDataFromDb("Expected Param") // then assertEquals("Expected Param", slot.captured) }

MutableList can be used to capture and store specific argument values for all method invocations:

@Test fun givenMock_whenCapturingParamsValues_thenProperValuesShouldBeCaptured() { // given val service = mockk() val list = mutableListOf() 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. Conclusion

Di artikel ini, kami telah membahas fitur paling penting dari MockK. MockK adalah pustaka yang kuat untuk bahasa Kotlin dan menyediakan banyak fitur yang berguna. Untuk informasi lebih lanjut tentang MockK, kami dapat memeriksa dokumentasi di situs web MockK.

Seperti biasa, kode sampel yang disajikan tersedia di GitHub.