Introduction to MockK, Part One

After we have finished this webinar, we:

  • Know what MockK is.
  • Can get the required dependencies with Maven and Gradle.
  • Know how we can create test doubles with MockK.
  • Understand how we can integrate MockK with JUnit 5.

This webinar assumes that:

Get the source code from Github

What Is MockK?

MockK is a mocking library for Kotlin. It’s key features are:

  • It supports mocks, stubs, and spies.
  • It supports both strict and relaxed stubs.
  • It has a very versatile support for argument matching.
  • We can create our test doubles by using either the manual approach or annotations.
  • It provides a built-in support for both JUnit 5 and Spring Boot.

Next, we will find out how we can get the required dependencies with Maven and Gradle.

Getting the Required Dependencies

If we are using Kotlin 1.3.0 and Coroutines 1.0 (or newer), we have to declare the mockk 1.9.3 dependency in our build script.

If we are using Maven, we can get this dependency by adding the following dependency declaration to the dependencies section of our POM file:

1<dependency>
2	<groupId>io.mockk</groupId>
3	<artifactId>mockk</artifactId>
4	<version>1.9.3</version>
5	<scope>test</scope>
6</dependency>

If we are using Gradle, we have to add the mockk dependency to the testImplementation dependency configuration. We can do this by adding the following line to the dependencies block of our build.gradle file:

1testImplementation('io.mockk:mockk:1.9.3')
If we are using Kotlin 1.2.0, we have to use the MockK version 1.9.3.kotlin12.

Let’s move on and find out how we can create our test doubles by using the manual approach.

Creating Test Doubles With the Manual Approach

Before we continue, I want to point out that MockK doesn’t differentiate stubs and mocks. This means that if we want to create either a stub or a mock, we have to use the same function called mockk().

This is a bit annoying because the name of this function suggests that the created object is a mock. That being said, I use this function here because it makes things a bit more simple.

Next, we will create stubs, mocks, and Spies with MockK. Note that all the following examples replace a list with the created test double.

Creating Stubs

If we want to create a strict stub, we have to follow these steps:

  1. Add a lateinit property to our test class and ensure that this property contains a list of Long objects.
  2. Add a setup function to our test class and ensure that this function is invoked before a test function is run. We will create our stub in this function.
  3. Create our stub by invoking the mockk() function and store the created stub in the list property. When we invoke the mockk() function, we have to configure the type of the created stub.

After we have created a strict stub, the source code of our test class looks as follows:

 1import io.mockk.mockk
 2import org.junit.jupiter.api.BeforeEach
 3import org.junit.jupiter.api.DisplayName
 4import org.junit.jupiter.api.Tag
 5
 6@Tag("unitTest")
 7@DisplayName("Demonstrates how we can create a strict stub")
 8class StrictStubExampleTest {
 9
10    private lateinit var list: List<Long>
11
12    @BeforeEach
13    fun createStub() {
14        list = mockk<List<Long>>()
15    }
16}

If we want to create a relaxed stub, we have to follow these steps:

  1. Add a lateinit property to our test class and ensure that this property contains a list of objects. We have to use the star projection here because relaxed stubbing isn’t working properly with generic return types.
  2. Add a setup function to our test class and ensure that this function is invoked before a test function is run. We will create our stub in this function.
  3. Create our stub by invoking the mockk() function and store the created stub in the list property. When we invoke the mockk() function, we have to configure the type of the created stub and set the value of the relaxed function parameter to true.

After we have created a relaxed stub, the source code of our test class looks as follows:

 1import io.mockk.mockk
 2import org.junit.jupiter.api.BeforeEach
 3import org.junit.jupiter.api.DisplayName
 4import org.junit.jupiter.api.Tag
 5
 6@Tag("unitTest")
 7@DisplayName("Demonstrates how we can create a relaxed stub")
 8class RelaxedStubExampleTest {
 9
10    private lateinit var list: List<*>
11
12    @BeforeEach
13    fun createStub() {
14        list = mockk<List<*>>(relaxed = true)
15    }
16}

Let’s move on and find out how we can create mocks with MockK.

Creating Mocks

If we want to create a strict mock, we have to follow these steps:

  1. Add a lateinit property to our test class and ensure that this property contains a list of Long objects.
  2. Add a setup function to our test class and ensure that this function is invoked before a test function is run. We will create our mock in this function.
  3. Create our mock by invoking the mockk() function and store the created mock in the list property. When we invoke the mockk() function, we have to configure the type of the created mock.

After we have created a strict mock, the source code of our test class looks as follows:

 1import io.mockk.mockk
 2import org.junit.jupiter.api.BeforeEach
 3import org.junit.jupiter.api.DisplayName
 4import org.junit.jupiter.api.Tag
 5
 6@Tag("unitTest")
 7@DisplayName("Demonstrates how we can create a strict mock")
 8class StrictMockExampleTest {
 9
10    private lateinit var list: List<Long>
11
12    @BeforeEach
13    fun createMock() {
14        list = mockk<List<Long>>()
15    }
16}

If we want to create a relaxed mock, we have to follow these steps:

  1. Add a lateinit property to our test class and ensure that this property contains a list of Long objects. Note that we don’t have to use the star projection here as long as we don’t stub the return values of the invoked functions.
  2. Add a setup function to our test class and ensure that this function is invoked before a test function is run. We will create our mock in this function.
  3. Create our mock by invoking the mockk() function and store the created mock in the list property. When we invoke the mockk() function, we have to configure the type of the created mock and set the value of the relaxed function parameter to true.

After we have created a relaxed mock, the source code of our test class looks as follows:

 1import io.mockk.mockk
 2import org.junit.jupiter.api.BeforeEach
 3import org.junit.jupiter.api.DisplayName
 4import org.junit.jupiter.api.Tag
 5
 6@Tag("unitTest")
 7@DisplayName("Demonstrates how we can create a relaxed mock")
 8class RelaxedMockExampleTest {
 9
10    private lateinit var list: List<Long>
11
12    @BeforeEach
13    fun createMock() {
14        list = mockk<List<Long>>(relaxed = true)
15    }
16}

If we want to mock or stub Kotlin objects, we have to follow these steps:

  1. Add a setup function to our test class and ensure that this function is invoked before a test function is run. We will create our mock or stub in this function.
  2. Replace the Kotlin object with the our test double. We can do this by invoking the mockkObject() function. When we invoke this function, we must specify the Kotlin object that’s replaced with our test double.
  3. Add a teardown function to our test class and ensure that this function is invoked after a test function has been run. We will destroy our test double in this function.
  4. Destroy our test double by invoking the unmockkObject() function. When we invoke this function, we must specify the Kotlin object that was replaced with a test double.

After we have replaced a Calculator object with a mock, the source code of our test class looks as follows:

 1import io.mockk.mockkObject
 2import io.mockk.unmockkObject
 3import org.junit.jupiter.api.AfterEach
 4import org.junit.jupiter.api.BeforeEach
 5import org.junit.jupiter.api.DisplayName
 6import org.junit.jupiter.api.Tag
 7
 8@Tag("unitTest")
 9@DisplayName("Demonstrates how we can mock Kotlin objects")
10class ObjectMockExampleTest {
11
12    @BeforeEach
13    fun createMock() {
14        mockkObject(Calculator)
15    }
16
17    @AfterEach
18    fun destroyMock() {
19        unmockkObject(Calculator)
20    }
21}

There are two things I want to point out:

  • We can use the this technique if we want to replace a Kotlin object with a stub.
  • If we have replaced multiple Kotlin objects with a test double, we can also destroy our test doubles by invoking the unmockkAll() function.

Additional Reading:

Next, we will learn to create spies with MockK.

Creating Spies

If we want to create a spy, we have to follow these steps:

  1. Add a lateinit property to our test class and ensure that this property contains a mutable list of Long objects.
  2. Add a setup function to our test class and ensure that this function is invoked before a test function is run. We will create our spy in this function.
  3. Create our spy by invoking the spyk() function and store the created spy in the list property. When we invoke the spyk() function, we have to configure the type of the created spy.

After we have created a spy, the source code of our test class looks as follows:

 1import io.mockk.spyk
 2import org.junit.jupiter.api.BeforeEach
 3import org.junit.jupiter.api.DisplayName
 4import org.junit.jupiter.api.Tag
 5
 6@Tag("unitTest")
 7@DisplayName("Demonstrates how we can create a spy")
 8class SpyExampleTest {
 9
10    private lateinit var list: MutableList<Long>
11
12    @BeforeEach
13    fun createSpy() {
14        list = spyk<ArrayList<Long>>()
15    }
16}

Additional Reading:

We can now create different test doubles by using the manual approach. Let’s move on and find out how we can create our test doubles by using MockK annotations.

Creating Test Doubles With Annotations

When we want to create test doubles by using the MockK annotations, we can use the following annotations:

  • The @MockK annotation allows us to create both strict and relaxed stubs and mocks. If we want to create a relaxed stub or a mock, we have to set the value of the @MockK annotation’s relaxed attribute to true.
  • The @RelaxedMockK annotation allows us to create relaxed stubs and mocks.
  • The @SpyK annotation allows us to create spies.

Next, we will create different test doubles by using these annotations. We can create our test doubles by following these steps:

  1. Add a lateinit property to our test class and ensure that this property contains a strict mock.
  2. Add a lateinit property to our test class and ensure that this property contains a relaxed mock.
  3. Add a new property to our test class and ensure that this property contains a spy.
  4. Add a lateinit property to our test class and ensure that this property contains a strict stub.
  5. Add a lateinit property to our test class and ensure that this property contains a relaxed stub.
  6. Add a setup function to our test class and ensure this function is invoked before a test function is run. We will create our test doubles in this function.
  7. Create our test doubles by invoking the static init() function of the MockKAnnotations class. When we invoke this function, we must configure the test object that contains the fields or properties which store our test doubles.

After we have written a test class that creates our test doubles with MockK annotations, its source code looks as follows:

 1import io.mockk.MockKAnnotations
 2import io.mockk.impl.annotations.MockK
 3import io.mockk.impl.annotations.RelaxedMockK
 4import io.mockk.impl.annotations.SpyK
 5import org.junit.jupiter.api.BeforeEach
 6import org.junit.jupiter.api.DisplayName
 7
 8@DisplayName("Demonstrates how we can create test doubles with annotations")
 9class AnnotationTest {
10
11    @MockK
12    private lateinit var mockList: List<Long>
13
14    @MockK(relaxed = true)
15    private lateinit var relaxedMockList: List<Long>
16
17    @SpyK
18    private var spyList = ArrayList<Long>()
19
20    @MockK
21    private lateinit var stubList: List<Long>
22
23    @RelaxedMockK
24    private lateinit var relaxedStubList: List<*>
25
26    @BeforeEach
27    fun createTestDoubles() {
28        MockKAnnotations.init(this)
29    }
30}

Before we continue, I want to point out that I am not a huge fan of using annotation for this purpose because they make our life too easy. The developers of different mocking libraries added annotation support to their libraries because the users of their libraries were complaining that creating test doubles requires too much code.

The problem is that annotations help us to hide the design problems found from our code. I think that if configuring the system under test is too cumbersome, the system under test has too many dependencies and we must refactor it. That’s why I think that we shouldn’t create our test double with annotations.

Additional Reading:

Next, we will find out how we can create our test doubles by using annotations and JUnit 5.

Integrating MockK With JUnit 5

If we are using JUnit 5, and we want to create our test doubles by using MockK annotations, we can create our test doubles by using the MockKExtension. When we want to use this extension, we have to make the following changes to the test class which created during the previous section:

  1. Remove the setup function from our test class.
  2. Ensure that the MockKExtension is invoked when we run the tests found from our test class

After we have made these changes to our test class, its source code looks as follows:

 1import io.mockk.impl.annotations.MockK
 2import io.mockk.impl.annotations.RelaxedMockK
 3import io.mockk.impl.annotations.SpyK
 4import io.mockk.junit5.MockKExtension
 5import org.junit.jupiter.api.DisplayName
 6import org.junit.jupiter.api.extension.ExtendWith
 7
 8@ExtendWith(MockKExtension::class)
 9@DisplayName("Demonstrates how we can integrate mockk with junit 5")
10class JUnit5Test {
11
12    @MockK
13    private lateinit var mockList: List<Long>
14
15    @MockK(relaxed = true)
16    private lateinit var relaxedMockList: List<Long>
17
18    @SpyK
19    private var spyList = ArrayList<Long>()
20
21    @MockK
22    private lateinit var stubList: List<Long>
23
24    @RelaxedMockK
25    private lateinit var relaxedStubList: List<*>
26}

Additional Reading:

We can now create our test doubles with MockK and we know how we can integrate it with JUnit 5. Let’s summarize what we learned from this webinar.

Summary

This webinar has taught us four things:

  • We can create mocks and stubs by using either the mockk() function, @MockK annotation, or the @RelaxedMockK annotation.
  • If we want mock Kotlin objects, we have to use the mockkObject() and unmockkObject() functions.
  • We can create spies by using either the spyk() function or the @SpyK annotation.
  • If we want to create our test doubles with annotations, we have to initialize our test doubles by using either the static init() function of the MockKAnnotations class or the MockKExtension class.

Get the source code from Github