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. Stubbing Functions Returning Unit

In Kotlin, if a function’s return type is Unit, it means the function doesn’t return anything. It’s pretty similar to Java’s void method.

Let’s add one Unit function to the TestableService class:

fun addHelloWorld(strList: MutableList<String>) {
    println("addHelloWorld() is called")
    strList += "Hello World!"
}

As the code above shows, when the addHelloWorld() function gets called, it prints a line to the console. Then, the String “Hello World” is added to the given MutableList<String> object.

In this section, let’s look at how to stub a function returning Unit. Usually, depending on different circumstances, we want to control a Unit function stub in two ways:

  • making the function call do nothing, or in other words, skipping the function execution
  • calling the real function

So next, we’ll take the addHelloWorld() function as an example and address how to achieve them using MockK.

11.1. Making the Function Do Nothing

There are several ways to skip a Unit function using MockK:

every { addHelloWorld(any()) } returns Unit
every { addHelloWorld(any()) } answers { Unit }
every { addHelloWorld(any()) } just runs

Some of them are not new to us, such as returns … and answers {…}. However, the last one, “every { … } just runs” is easy to understand.

So next, let’s first test if it can skip the original function’s execution and then understand how “just runs” works:

@Test
fun givenServiceMock_whenCallingMethodReturnsUnit_thenCorrectlyVerified() {
    // given
    val service = mockk<TestableService>()
    val myList = mutableListOf<String>()
                                                                           
    // when
    every { service.addHelloWorld(any()) } just runs
    service.addHelloWorld(myList)
                                                                           
    // then
    assertTrue(myList.isEmpty())
}

As the test above shows, we stub the addHelloWorld() function using “just runs“. And then, we call the function and pass an empty MutableList to it.

If we skip the function’s execution successfully, “Hello World!” shouldn’t be added to the given list after the invocation.

The test passes if we give it a run.

Now let’s understand how “just runs” skips the real function call. First, let’s have a look at the just() function’s implementation:

infix fun MockKStubScope<Unit, Unit>.just(runs: Runs) = answers(ConstantAnswer(Unit))

As we can see, it’s an infix function. Therefore, we can write just(runs) in a better readable form: just runs. Further, Runs is a dummy object, and runs is merely a typealias to Runs:

object Runs
typealias runs = Runs

Moreover, if we stub a Unit function as just runs, it calls the answers() function and returns a constant answer: Unit.

11.2. Calling the Original Function

Now, let’s see how to stub a Unit function to make it call the real function. To call the original function, we can use this approach: every { … } answers { callOriginal() }.

So next, let’s see how it’s used in a real test:

@Test
fun givenServiceMock_whenCallingOriginalMethod_thenCorrectlyVerified() {
    // given
    val service = mockk<TestableService>()
    val myList = mutableListOf<String>()
                                                                        
    // when
    every { service.addHelloWorld(any()) } answers { callOriginal() }
    service.addHelloWorld(myList)
                                                                        
    // then
    assertEquals(1, myList.size)
    assertEquals("Hello World!", myList.first())
}

The test passes when we run it. So, “Hello World!” is added to myList after calling the function. However, we may ask, if we stub a function and ask it to call the original implementaion, why do we bother to stub it?

So next, let’s see an example of when it’s useful.

11.3. When Do We Need callOriginal()?

Let’s say in our test, we want to call the addHelloWorld() function with different parameters. If the list passing to the function contains the string “Kai”, we want to call the actual function. Otherwise, we’d like to skip the function call:

@Test
fun givenServiceMock_whenStubbingTwoScenarios_thenCorrectlyVerified() {
    // given
    val service = mockk<TestableService>()
    val kaiList = mutableListOf("Kai")
    val emptyList = mutableListOf<String>()
                                                                                     
    // when
    every { service.addHelloWorld(any()) } just runs
    every { service.addHelloWorld(match { "Kai" in it }) } answers { callOriginal() }
                                                                                     
    service.addHelloWorld(kaiList)
    service.addHelloWorld(emptyList)
                                                                                     
    // then
    assertEquals(listOf("Kai", "Hello World!"), kaiList)
    assertTrue(emptyList.isEmpty())
}

As we can see, we’ve stubbed the addHelloWorld() function twice, depending on our requirements. Therefore, callOriginal() allows us to decide function stubbings’ behaviors flexibly.

12. 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.

1 Comment
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!