Stubbing Functions With MockK

After we have finished this webinar, we:

  • Can return the same object every time when the specified function is invoked.
  • Know how we can return different objects on subsequent function invocations.
  • Can throw an exception when the specified function is invoked.
  • Understand how we can specify the action that’s run when a stubbed function is invoked.

Get the source code from Github

Introduction to System Under Test

During this webinar we will stub the functions of a repository which provides CRUD operations for Task objects. Because I wanted to keep things as simple as possible, I added the declarations of my data model components to a single source file called Task.kt. This file looks as follows:

 1data class Creator(val id: Long)
 2
 3enum class TaskResolution {
 4    DUPLICATE,
 5    DONE,
 6    WONT_DO
 7}
 8
 9enum class TaskStatus {
10    CLOSED,
11    IN_PROGRESS,
12    OPEN
13}
14
15data class Task(val id: Long?,
16                val creator: Creator,
17                val title: String,
18                val status: TaskStatus,
19                val resolution: TaskResolution?)

The TaskRepository interface has two functions which we stub during this webinar:

  • The findById() function returns the information of one task by using its id as search criteria. If no task is found from the database, this function returns null.
  • The create() function inserts a new task into the database and returns the information of the created task.

The source code of the TaskRepository interface looks as follows:

1interface TaskRepository {
2
3    fun findById(id: Long): Task?
4
5    fun create(task: Task): Task
6}

Next, we will find out how we can stub these functions with MockK.

Stubbing Functions With MockK

When we stub a function with MockK, we have to follow these steps:

  1. Invoke the every() function.
  2. Specify the stubbed function and the expected function parameters.
  3. Configure the response of the function invocation.

Let’s take a look at four examples which demonstrate how we can stub functions with MockK.

Example 1: Returning the Same Object on Every Function Invocation

If we want to return the same object every time when the specified function is invoked by using the expected function parameters, we have to configure the returned object by using the returns() function. This function takes the returned object as a function parameter.

Let’s ensure that the findById() function of the TaskRepository interface returns an open task when it’s invoked by using the function parameter: 5L. After we have stubbed this function, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.junit.jupiter.api.BeforeEach
 4import org.junit.jupiter.api.DisplayName
 5import org.junit.jupiter.api.Tag
 6import org.junit.jupiter.api.Test
 7
 8@Tag("unitTest")
 9@DisplayName("Demonstrates how we can stub functions with MockK")
10class TaskRepositoryStubTest {
11
12    companion object {
13
14        private const val CREATOR_ID = 5L
15        private const val TASK_ID = 1L
16        private const val TITLE = "Example task"
17    }
18
19    private lateinit var repository: TaskRepository
20
21    @BeforeEach
22    fun createStrictStub() {
23        repository = mockk()
24    }
25
26    @Test
27    @DisplayName("Should return the specified object")
28    fun shouldReturnSpecifiedObject() {
29        every { repository.findById(TASK_ID) } returns Task(
30                id = TASK_ID,
31                creator = Creator(id = CREATOR_ID),
32                title = TITLE,
33                status = TaskStatus.OPEN,
34                resolution = null
35        )
36    }
37}

Example 2: Returning Different Objects on Subsequent Function Invocations

If we want to return different objects on subsequent function invocations, we have to configure the returned objects by using the returnsMany() function which takes a List<T> object as a function parameter. This list contains the returned objects.

In other words, when the stubbed function is invoked for the first time, MockK returns the first item of the list. The subsequent function invocations always return the next item of the list. Also, if MockK runs out of list items, it returns the last item of the list.

Let’s ensure that the findById() function of the TaskRepository interface returns two different Task objects when it’s invoked by using the function parameter: 5L. The first invocation returns an open task and the second invocation returns a task that was closed because it was finished.

After we have stubbed this function, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.junit.jupiter.api.BeforeEach
 4import org.junit.jupiter.api.DisplayName
 5import org.junit.jupiter.api.Tag
 6import org.junit.jupiter.api.Test
 7
 8@Tag("unitTest")
 9@DisplayName("Demonstrates how we can stub functions with MockK")
10class TaskRepositoryStubTest {
11
12    companion object {
13
14        private const val CREATOR_ID = 5L
15        private const val TASK_ID = 1L
16        private const val TITLE = "Example task"
17    }
18
19    private lateinit var repository: TaskRepository
20
21    @BeforeEach
22    fun createStrictStub() {
23        repository = mockk()
24    }
25
26    @Test
27    @DisplayName("Should return different object on different invocations")
28    fun shouldReturnDifferentObjectsOnDifferentInvocations() {
29        every { repository.findById(TASK_ID) } returnsMany listOf(
30                Task(
31                        id = TASK_ID,
32                        creator = Creator(id = CREATOR_ID),
33                        title = TITLE,
34                        status = TaskStatus.OPEN,
35                        resolution = null
36                ),
37                Task(
38                        id = TASK_ID,
39                        creator = Creator(id = CREATOR_ID),
40                        title = TITLE,
41                        status = TaskStatus.CLOSED,
42                        resolution = TaskResolution.DONE
43                )
44        )
45    }
46}

Example 3: Throwing an Exception

If we want to throw an exception when the specified function is invoked by using the expected function parameters, we have to configure the thrown exception by using the throws() function. This function takes the thrown exception object as a function parameter.

Let’s ensure that the findById() function of the TaskRepository interface throws a RuntimeException when it’s invoked by using the function parameter: 5L. After we have stubbed this function, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.junit.jupiter.api.BeforeEach
 4import org.junit.jupiter.api.DisplayName
 5import org.junit.jupiter.api.Tag
 6import org.junit.jupiter.api.Test
 7import java.lang.RuntimeException
 8
 9@Tag("unitTest")
10@DisplayName("Demonstrates how we can stub functions with MockK")
11class TaskRepositoryStubTest {
12
13    companion object {
14
15        private const val CREATOR_ID = 5L
16        private const val TASK_ID = 1L
17        private const val TITLE = "Example task"
18    }
19
20    private lateinit var repository: TaskRepository
21
22    @BeforeEach
23    fun createStrictStub() {
24        repository = mockk()
25    }
26
27    @Test
28    @DisplayName("Should throw an exception")
29    fun shouldThrowException() {
30        every { repository.findById(TASK_ID) } throws
31                RuntimeException("an exception")
32    }
33}

When we use this technique, we must not break the API constract of the stubbed function. This is important because we want to ensure that the system under test uses its dependencies in the correct way.

Additional Reading:

Example 4: Configuring the Executed Action

If we want to configure the action that’s run when the specified function is invoked by using the expected function parameters, we have to use the answers() function. When we invoke this function, we have to configure the invoked action by using a lambda expression. Also, if the the stubbed function returns an object, we must also configure the returned object.

Let’s ensure that the create() function of the TaskRepository interface sets the id of the created task before it returns a Task object that contains the returned information. After we have stubbed this function, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.junit.jupiter.api.BeforeEach
 4import org.junit.jupiter.api.DisplayName
 5import org.junit.jupiter.api.Tag
 6import org.junit.jupiter.api.Test
 7
 8@Tag("unitTest")
 9@DisplayName("Demonstrates how we can stub functions with MockK")
10class TaskRepositoryStubTest {
11
12    companion object {
13
14        private const val CREATOR_ID = 5L
15        private const val TASK_ID = 1L
16        private const val TITLE = "Example task"
17    }
18
19    private lateinit var repository: TaskRepository
20
21    @BeforeEach
22    fun createStrictStub() {
23        repository = mockk()
24    }
25
26    @Test
27    @DisplayName("Should configure the executed action")
28    fun shouldConfigureExecutedAction() {
29        every { repository.create(any()) } answers {
30            val input = this.firstArg<Task>()
31
32            Task(
33                    id = 1L,
34                    creator = input.creator,
35                    title = input.title,
36                    status = input.status,
37                    resolution = input.resolution
38            )
39        }
40    }
41}

Summary

This webinar has taught us five things:

  • When we want to stub a function with MockK, we have to use the every() function.
  • If we want to return the same object every time when the specified function is invoked by using the expected function parameters, we have to configure the returned object by using the returns() function.
  • If we want to return different objects on subsequent function invocations, we have to configure the returned objects by using the returnsMany() function.
  • If we want to throw an exception when the specified function is invoked by using the expected function parameters, we have to configure the thrown exception by using the throws() function.
  • If we want to configure the action that’s run when the specified function is invoked by using the expected function parameters, we have to use the answers() function.

Get the source code from Github