fbpx

A Guide to MockK: a Mocking Library for Kotlin

1. Introduction

There is no doubt, that testing is really crucial in the development process of any project. It allows us to automate the process of checking the functionality, as well as improves the quality of the code.

One of the techniques commonly used in unit testing is mocking. To put it in simple terms, mock objects are the objects that simulate the behavior of real objects.

In this article, I’d like to show you how to use MockK– an open-source mocking library for Kotlin- with JUnit 5.

2. Prepare the Code For Testing

Before we will head to the testing part, let’s write the code, which we will be testing later:

class ExampleClass {

    fun multiplyByTen(number: Int) = 10 * number

    fun publicFunction() = privateFunction()

    private fun privateFunction() = "Returned value"
}

class Injected {
    fun multiplyByFive(number: Int) = 5 * number
}

class ExampleClassWithDependency {

    lateinit var injected: Injected

    fun returnInjectedValue(number: Int) = injected.multiplyByFive(number)
}

object ExampleObject {
    fun concat(one: String, two: String) = one + two
}

As you can see, we’ve created a few simple classes and one object. Just for the record, a Kotlin object is a special singleton class- a class that has got only one instance.

3. Imports

As the next step, let’s add the required dependencies to our project:

  
  implementation("io.mockk:mockk:1.10.2")
  testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0")
  testImplementation("org.junit.jupiter:junit-jupiter-engine:5.7.0")

3. Using MockK library

In this article, we will learn how to use a few simple features of the library. If you would like to learn more, I highly recommend checking out MockK’s Github repository.

3.1. Mocking

Firstly, let’s see how to specify what exactly should be returned by the function of the mocked class:

    @Test
    fun `Mock a class`() {
        val exampleClass = mockk()

        every { exampleClass.publicFunction() } returns "Mocked value"

        val result = exampleClass.publicFunction()

        assertEquals("Mocked value", result)
    }

With this code, the “Mocked value” String will be returned for each invocation of the publicFunction.

Nextly, let’s mock a property of the class:

    @Test
    fun `Mock a property of the class`() {
        val injected = mockk()
        val exampleClass = ExampleClassWithDependency()
        exampleClass.injected = injected

        every { injected.multiplyByFive(any()) } returns 7

        val result = exampleClass.returnInjectedValue(10)

        assertEquals(7, result)
    }

This time, our result will be 7, even though the original function should return 50.

3.2. Relaxed mock

What happens if we do not specify the behavior of the invoked function? Let’s check this code:

    @Test
    fun `Example of MockKException`() {
        val exampleClass = mockk()

        exampleClass.publicFunction()
    }

The test failed throwing MockKException informing us that no answer has been found for the function we were trying to test.

    @Test
    fun `Fix MockKException using relaxed mock`() {
        val exampleClass = mockk(relaxed = true)
        val defaultStringValue = ""

        assertEquals(defaultStringValue, exampleClass.publicFunction())
    }

To fix that, we can either specify explicitly the behavior or use a relaxed mock. A relaxed mock returns the default values for all the functions within the mocked class. As we can see above, the default String value returned by the function will be an empty String.

3.3. Using Annotations

If we would like to simplify the creation of the object, we can use annotations. As we are using JUnit5, we need to annotate our test class with @ExtendWith:

@ExtendWith(MockKExtension::class)
internal class ExampleClassTest

As the next step, let’s add two properties to it:

    @MockK
    lateinit var annotatedMock: Injected

    @InjectMockKs
    var annotatedClass = ExampleClassWithDependency()

And create a new test:

    @Test
    fun `Simplified property mock with annotation`() {
        every { annotatedMock.multiplyByFive(any()) } returns 7

        val result = annotatedClass.returnInjectedValue(10)

        assertEquals(7, result)
    }

With this approach, we eliminate the need to manually create the mock for each test.

3.4. Spy The Object

Sometimes, we would like to have the possibility to check the real behavior of the class, as well as a mocked one. For that case, we can use a spy:

    @Test
    fun `Spy a class`() {
        val exampleClass = spyk()

        assertEquals("Returned value", exampleClass.publicFunction())
    }

As we can see here, the function returns the value that has been implemented in our code.

3.5. Mock Private Function Behavior

In our class implementation, publicFunction returns the value from privateFunction. Let’s check, how we can specify the behavior of the private function:

@Test
fun `Mock a private function`() {
val exampleClass = spyk(recordPrivateCalls = true)

every { exampleClass["privateFunction"]() } returns "Mocked value"

assertEquals("Mocked value", exampleClass.publicFunction())
}

3.6. Mock an Object

As we’ve told in the beginning, the Kotlin object is a special singleton class. To mock it, we will use mockkObject:

    @Test
    fun `Mock an object`() {
        mockkObject(ExampleObject)

        every { ExampleObject.concat(any(), any()) } returns "Mocked value"

        val result = ExampleObject.concat("", "")
        assertEquals("Mocked value", result)
    }

Even though Kotlin allows us to have only one instance of the object class, the MockK library allows us to create multiple instances of its mocks:

 
    @Test
    fun `Multiple mocked instances of object`() {
        val firstMock = mockk()
        val secondMock = mockk()

        every { firstMock.concat(any(), any()) } returns "One"
        every { secondMock.concat(any(), any()) } returns "Two"

        val firstResult = firstMock.concat("", "")
        val secondResult = secondMock.concat("", "")

        assertEquals("One", firstResult)
        assertEquals("Two", secondResult)
    }

3.7. Capturing

In our previous examples, we didn’t care about what arguments were passed to the stubbed methods. A technique called capturing allows us to validate what arguments has been passed:

    @Test
    fun `Capture passed value`() {
        val exampleClassMock = mockk()
        val argumentSlot = slot()

        every { exampleClassMock.multiplyByTen(capture(argumentSlot)) } returns 5

        exampleClassMock.multiplyByTen(55)

        assertEquals(55, argumentSlot.captured)
    }

As we can see, the slot contains the captured value, which we have passed to our checked function.

3.8. Verification

If we would like to check how many times the function has been invoked, we can use verify:

    @Test
    fun `Verify calls`() {
        val exampleClassMock = mockk()

        every { exampleClassMock.multiplyByTen(any()) } returns 5

        exampleClassMock.multiplyByTen(10)
        exampleClassMock.multiplyByTen(20)

        verify(exactly = 2) { exampleClassMock.multiplyByTen(any()) }
        confirmVerified(exampleClassMock)
    }

In the above example, we’ve used the exactly parameter to verify that the behavior happened exactly 2 times. If we would specify another number, the test would fail and inform us, that the verification failed.

4. Summary

And that would be all for this article. We’ve learned how to use some basic features of the MockK library with JUnit5.

For the source code of the project, please visit our project on GitHub.

If you enjoyed this tutorial, I would be forever grateful if you would like to share some feedback with me through our page, group, or contact form. I highly appreciate all the comments and suggestions that I am getting from you.

Share on facebook
Share on twitter
Share on linkedin

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Join the community and get free eBooks.

Image shows the covers of free ebooks accessible for newsletter subscribers.

You may opt out any time. Terms of Use and Privacy Policy

Find us also on...

Join the FREE weekly newsletter and get two free eBooks as well:

Image shows the covers of free ebooks accessible for newsletter subscribers.

You may opt out any time. Terms of Use and Privacy Policy

To make Codersee work, we log user data. By using our site, you agree to our Privacy Policy and Terms of Use.