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.
This webinar assumes that:
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 returnsnull. - 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:
- Invoke the
every()function. - Specify the stubbed function and the expected function parameters.
- Configure the response of the function invocation.
Additional Reading:
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}
Additional Reading:
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}
Additional Reading:
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}
Additional Reading:
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.