MockK: A Mocking Library for Kotlin

1. Overview

In this tutorial, we’re going to take a look at some of the basic features of the MockK library.

2. MockK

In Kotlin, all classes and methods are final. While this helps us write immutable code, it also causes some problems during testing.

Most JVM mock libraries have problems with mocking or stubbing final classes. Of course, we can add the “open” keyword to classes and methods that we want to mock. But changing classes only for mocking some code doesn’t feel like the best approach.

Here comes the MockK library, which offers support for Kotlin language features and constructs. MockK builds proxies for mocked classes. This causes some performance degradation, but the overall benefits that MockK gives us are worth it.

3. Installation

Installation is as simple as it can be. We just need to add the mockk dependency to our Maven project:

<dependency>
    <groupId>io.mockk</groupId>
    <artifactId>mockk</artifactId>
    <version>1.9.3</version>
    <scope>test</scope>
</dependency>

For Gradle, we need to add it as a test dependency:

testImplementation "io.mockk:mockk:1.9.3"

4. Basic Example

Let’s create a service that we’d like to mock:

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

    fun doSomethingElse(testParameter: String): String {
        return "I don't want to!"
    }
}

Here’s an example test that mocks 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)
}

To define the mock object, we’ve used the mockk<…>() method.

In the next step, we defined the behavior of our mock. For this purpose, we’ve created an every block that describes what response should be returned for which call.

Finally, we used the verify block to verify whether the mock was invoked as we expected.

5. Annotation Example

It is possible to use MockK annotations to create all kind of mocks. Let’s create a service that requires two instances of our TestableService:

class InjectTestService {
    lateinit var service1: TestableService
    lateinit var service2: TestableService

    fun invokeService1(): String {
        return service1.getDataFromDb("Test Param")
    }
}

InjectTestService contains two fields with the same type. It won’t be a problem for MockK. MockK tries to match properties by name, then by class or superclass. It also has no problem with injecting objects into private fields.

Let’s mock InjectTestService in a test by using annotations:

class AnnotationMockKUnitTest {

    @MockK
    lateinit var service1: TestableService

    @MockK
    lateinit var service2: TestableService

    @InjectMockKs
    var objectUnderTest = InjectTestService()

    @BeforeEach
    fun setUp() = MockKAnnotations.init(this)

    // Tests here
    ...
}

In the above example, we’ve used the @InjectMockKs annotation. This specifies an object where defined mocks should be injected. By default, it injects variables that are not assigned yet. We can use @OverrideMockKs to override fields that have a value already.

MockK requires MockKAnnotations.init(…) to be called on an object declaring a variable with annotations. For Junit5, it can be replaced with @ExtendWith(MockKExtension::class).

6. Spy

Spy allows mocking only a particular part of some class. For example, it can be used to mock a specific method in 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)
}

In the example, we’ve used the spyk method to create a spy object. We could’ve also used the @SpyK annotation to achieve the same:

class SpyKUnitTest {

    @SpyK
    lateinit var service: TestableService

    // Tests here
}

7. Relaxed Mock

A typical mocked object will throw MockKException if we try to call a method where the return value hasn’t been specified.

If we don’t want to describe the behavior of each method, then we can use a relaxed mock. This kind of mock provides default values for each function. For example, the String return type will return an empty String. Here’s a short example:

@Test
fun givenRelaxedMock_whenCallingNotMockedMethod_thenReturnDefaultValue() {
    // given
    val service = mockk<TestableService>(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<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. 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<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 can be used to capture and store specific argument values for all method invocations:

@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. Conclusion

In this article, we’ve discussed the most important features of MockK. MockK is a powerful library for the Kotlin language and provides a lot of useful features. For more information about MockK, we can check the documentation on the MockK website.

As always, the sample code presented is available over on over on GitHub.

Leave a Reply

Your email address will not be published.