MockK: Perpustakaan Mengejek untuk Kotlin

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan melihat beberapa ciri asas perpustakaan MockK.

2. Mengejek

Di Kotlin, semua kelas dan kaedah adalah muktamad. Walaupun ini membantu kita menulis kod yang tidak berubah, ia juga menyebabkan beberapa masalah semasa ujian.

Sebilangan besar perpustakaan tiruan JVM mempunyai masalah dengan mengejek atau mengongkong kelas akhir. Sudah tentu, kita boleh menambah kata kunci " terbuka " ke kelas dan kaedah yang kita ingin mengejek. Tetapi menukar kelas hanya kerana mengejek beberapa kod tidak terasa seperti pendekatan terbaik.

Di sinilah perpustakaan MockK, yang menawarkan sokongan untuk ciri dan konstruksi bahasa Kotlin. MockK membina proksi untuk kelas diejek. Ini menyebabkan kemerosotan prestasi, tetapi faedah keseluruhan yang MockK berikan kepada kita sangat berbaloi.

3. Pemasangan

Pemasangan semudah mungkin. Kita hanya perlu menambahkan pergantungan tiruan pada projek Maven kita:

 io.mockk mockk 1.9.3 test 

Untuk Gradle, kita perlu menambahkannya sebagai ketergantungan ujian:

testImplementation "io.mockk:mockk:1.9.3"

4. Contoh Asas

Mari buat perkhidmatan yang ingin kami 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 ujian 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 kaedah mockk () .

Pada langkah seterusnya, kami menentukan tingkah laku ejekan kami. Untuk tujuan ini, kami telah membuat setiap blok yang menerangkan tindak balas apa yang harus dikembalikan untuk panggilan mana.

Akhir sekali, kami menggunakan mengesahkan blok untuk mengesahkan sama ada olok-olok yang telah digunakan seperti yang kita harapkan .

5. Contoh Anotasi

Adalah mungkin untuk menggunakan anotasi MockK untuk membuat semua jenis ejekan. Mari buat perkhidmatan yang memerlukan dua contoh TestableService kami :

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

InjectTestService mengandungi dua medan dengan jenis yang sama. Ia tidak akan menjadi masalah bagi MockK. MockK cuba memadankan sifat dengan nama, kemudian mengikut kelas atau superclass. Ia juga tidak mempunyai masalah dengan menyuntik objek ke medan persendirian .

Mari mengejek InjectTestService dalam ujian 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 ejekan yang ditentukan harus disuntik. Secara lalai, ia menyuntikkan pemboleh ubah yang belum ditetapkan. Kita boleh menggunakan @OverrideMockKs untuk mengatasi medan yang sudah mempunyai nilai.

MockK menghendaki MockKAnnotations.init (…) dipanggil pada objek yang menyatakan pemboleh ubah dengan anotasi. Untuk Junit5, ia boleh diganti dengan @ExtendWith (MockKExtension :: class) .

6. Perisik

Perisik membenarkan ejekan hanya bahagian tertentu dari beberapa kelas. Sebagai contoh, ia boleh digunakan untuk mengejek kaedah 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) }

Sebagai contoh, kami telah menggunakan kaedah spyk untuk membuat objek mata-mata. Kami juga boleh menggunakan anotasi @SpyK untuk mencapai yang sama:

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

7. Mengejek Santai

Objek tiruan biasa akan melemparkan MockKException jika kita cuba memanggil kaedah di mana nilai pengembalian belum ditentukan.

Sekiranya kita tidak mahu menggambarkan tingkah laku setiap kaedah, maka kita dapat menggunakan ejekan santai. Jenis tiruan ini memberikan nilai lalai untuk setiap fungsi. Contohnya, String return type akan mengembalikan String kosong . Berikut adalah contoh ringkas:

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

In the example, we've used the mockk method with the relaxed attribute to create a relaxed mock object. We could've also used the @RelaxedMockK annotation:

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

8. Object Mock

Kotlin provides an easy way to declare a singleton by using the object keyword:

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

However, most of the mocking libraries have a problem with mocking Kotlin singleton instances. Because of this, MockK provides the mockkObject method. Let's take a look:

@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. Hierarchical Mocking

Another useful feature of MockK is the ability to mock hierarchical objects. First, let's create a hierarchical object structure:

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

Dalam artikel ini, kami telah membincangkan ciri-ciri MockK yang paling penting. MockK adalah perpustakaan yang hebat untuk bahasa Kotlin dan menyediakan banyak ciri berguna. Untuk maklumat lebih lanjut mengenai MockK, kami dapat memeriksa dokumentasi di laman web MockK.

Seperti biasa, contoh kod yang disajikan tersedia berulang-ulang di GitHub.