Verifying Function Invocations With MockK
After we have finished this lesson, we understand how we can verify interactions which happened between the system under test and a mock.
This webinar assumes that:
Get the source code from Github
Introduction to the System Under Test
During this lesson we will verify the interactions which happened between the system under test and a TaskRepository mock. The TaskRepository interface declares the functions which provide 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 three functions:
- The
findById()function returns the information of one task by using itsidas search criteria. If no task is found from the database, this function returnsnull. - The
findByCreatorAndStatus()function returns all tasks which were created by the specified person and have the given status. If no tasks are found from the database, this function returns an empty list. - 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 findByCreatorAndStatus(creatorId: Long, status: TaskStatus): List<Task>
6
7 fun create(task: Task): Task
8}
Next, we will learn to verify function invocations with MockK.
Verifying Function Invocations With MockK
When we have to verify the interactions which happened between the system under test and a mock, we can use these functions:
- The
verify()function allows us to verify that the system under test invoked the specified function X times by using the expected function parameters. We can specify the number of expected function invocations by using these function parameters:- The
atLeastparameter allows us to verify that the specified function was invoked at least X times. - The
atMostparameter helps us to ensure that the specified function was invoked at most X times. - The
exactlyparameter allows us to specify the exact number of the expected function invocations.
- The
- The
confirmVerified()function helps us to ensure that our test function verifies all interactions between the system under test and the specified mock objects. This function is typically used together with theverify()function. - The
verifyAll()function verifies all specified function invocations. This function ignores the order of function invocations. - The
verifyOrder()function ensures that the specified function invocations happened in the expected order. - The
verifySequence()function verifies that the specified function invocations happened in the expected order and ensures that there are no other interactions between the system under test and our mock.
Additional Reading:
Let’s take a look at eight examples which demonstrate how we can verify the interactions which happened between the system under test and a mock.
Example 1: Verifying That a Function Wasn’t Invoked
If we have to verify that a function wasn’t invoked by using the specified function parameters, we have to use the verify() function. When we invoke this function, we have to specify the number of expected function invocations by setting the value of the exactly function parameter to 0. Also, we have to specify the function invocation which shouldn’t happen.
Let’s verify that the create() function of the TaskRepository interface wasn’t invoked. After we have specified our expectation, the source code of our test function looks as follows:
1import io.mockk.mockk
2import io.mockk.verify
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("Verify interactions between SUT and a mock")
10class TaskRepositoryMockTest {
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 createMock() {
23 repository = mockk(relaxed = true)
24 }
25
26 @Test
27 @DisplayName("Should verify that the create() function was not invoked")
28 fun shouldVerifyThatCreateFunctionWasNotInvoked() {
29 repository.findById(TASK_ID)
30
31 verify(exactly = 0) { repository.create(any()) }
32 }
33}
Example 2: Verify That a Function Was Invoked Once
If we have to verify that a function was invoked once by using the specified function parameters, we have to use the verify() function. When we invoke this function, we have to specify the number of expected function invocations by setting the value of the exactly function parameter to 1. Also, we have to specify the invoked function and the expected function parameters.
Let’s verify that the findById() function of the TaskRepository interface was invoked once by using the function parameter 1L. After we have specified our expectation, the source code of our test function looks as follows:
1import io.mockk.mockk
2import io.mockk.verify
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("Verify interactions between SUT and a mock")
10class TaskRepositoryMockTest {
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 createMock() {
23 repository = mockk(relaxed = true)
24 }
25
26 @Test
27 @DisplayName("Should verify that findById() function was invoked once")
28 fun shouldVerifyThatFindByIdFunctionWasInvokedOnce() {
29 repository.findById(TASK_ID)
30
31 verify(exactly = 1) { repository.findById(TASK_ID) }
32 }
33}
Example 3: Verifying That There Are No Unverified Function Invocations
If we have to verify that there are no unverified function invocations, we have to use the confirmVerified() function. When we invoke this function, we have to pass the inspected mock objects as a function parameter.
Let’s ensure that our test function invoked only the findById() function of the TaskRepository interface by using the function parameter: 1L. After we have specified our expectation, the source code of our test function looks as follows:
1import io.mockk.confirmVerified
2import io.mockk.mockk
3import io.mockk.verify
4import org.junit.jupiter.api.BeforeEach
5import org.junit.jupiter.api.DisplayName
6import org.junit.jupiter.api.Tag
7import org.junit.jupiter.api.Test
8
9@Tag("unitTest")
10@DisplayName("Verify interactions between SUT and a mock")
11class TaskRepositoryMockTest {
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 createMock() {
24 repository = mockk(relaxed = true)
25 }
26
27 @Test
28 @DisplayName("Should verify that there are no unverified invocations")
29 fun shouldVerifyThatThereAreNoUnverifiedInvocations() {
30 repository.findById(TASK_ID)
31
32 verify(exactly = 1) { repository.findById(TASK_ID) }
33 confirmVerified(repository)
34 }
35}
Example 4: Verifying That Only the Specified Function Invocations Happened in Any Order
If we have to verify that only the specified function invocations happened in any order, we have to specify the expected function invocations by using the verifyAll() function.
Let’s verify that our test function invoked the findById() function twice by using the function parameters: 1L and 5L. After we have specified our expectation, the source code of our test function looks as follows:
1import io.mockk.mockk
2import io.mockk.verifyAll
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("Verify interactions between SUT and a mock")
10class TaskRepositoryMockTest {
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 createMock() {
23 repository = mockk(relaxed = true)
24 }
25
26 @Test
27 @DisplayName("Should verify invocations but ignore order")
28 fun shouldVerifyInvocationsButIgnoreOrder() {
29 repository.findById(5L)
30 repository.findById(TASK_ID)
31
32 verifyAll {
33 repository.findById(TASK_ID)
34 repository.findById(5L)
35 }
36 }
37}
Example 5: Verifying That Only the Specified Function Invocations Happened in the Given Order
If we have to verify that only the specified function invocations happened in the given order, we have to specify the expected function invocations by using the verifySequence() function.
Let’s verify that our test function invoked the findById() function three times by using the function parameters:5L, 1L, and 99L. After we have specified our expectation, the source code of our test function looks as follows:
1import io.mockk.mockk
2import io.mockk.verifySequence
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("Verify interactions between SUT and a mock")
10class TaskRepositoryMockTest {
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 createMock() {
23 repository = mockk(relaxed = true)
24 }
25
26 @Test
27 @DisplayName("Should verify a sequence of function invocations")
28 fun shouldVerifySequenceOfFunctionInvocations() {
29 repository.findById(5L)
30 repository.findById(TASK_ID)
31 repository.findById(99L)
32
33 verifySequence {
34 repository.findById(5L)
35 repository.findById(TASK_ID)
36 repository.findById(99L)
37 }
38 }
39}
Example 6: Verifying That the Specified Function Invocations Happened in the Given Order
If we have to verify that the specified function invocations happened in the given order, we have to specify the expected function invocations by using the verifyOrder() function.
Let’s verify that our test function invoked the findById() function twice by by using the function parameters: 5L and 99L. After we have specified our expectation, the source code of our test function looks as follows:
1import io.mockk.mockk
2import io.mockk.verifyOrder
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("Verify interactions between SUT and a mock")
10class TaskRepositoryMockTest {
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 createMock() {
23 repository = mockk(relaxed = true)
24 }
25
26 @Test
27 @DisplayName("Should verify the order of function invocations")
28 fun shouldVerifyOrderOfFunctionInvocations() {
29 repository.findById(5L)
30 repository.findById(TASK_ID)
31 repository.findById(99L)
32
33 verifyOrder {
34 repository.findById(5L)
35 repository.findById(99L)
36 }
37 }
38}
Example 7: Using Simple Argument Matchers
If we want to specify the expected function parameters by using simple argument matchers, we can use these matchers when we specify the expected function invocation.
For example, let’s verify that our test function invoked the findByCreatorAndStatus() function of the TaskRepository interface once by using the function parameters: any Long object and TaskStatus.OPEN.
After we have specified our expectation, the source code of our test function looks as follows:
1import io.mockk.mockk
2import io.mockk.verify
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("Verify interactions between SUT and a mock")
10class TaskRepositoryMockTest {
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 createMock() {
23 repository = mockk(relaxed = true)
24 }
25
26 @Test
27 @DisplayName("Should use a simple argument matcher")
28 fun shouldUseSimpleArgumentMatcher() {
29 repository.findByCreatorAndStatus(1L, TaskStatus.OPEN)
30
31 verify(exactly = 1) { repository.findByCreatorAndStatus(
32 any(),
33 TaskStatus.OPEN
34 ) }
35 }
36}
Example 8: Specifying the Expected Function Parameter With a Predicate
If we want to specify the expected function parameter by using a predicate, we have to specify the predicate by using the match() function when we specify the expected function invocation.
Let’s verify that our test function invoked the create() function of the TaskRepository interface once and ensure that id of the Task object given as a function parameter is equal to 1L.
After we have specified our expectation, the source code of our test function looks as follows:
1import io.mockk.mockk
2import io.mockk.verify
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("Verify interactions between SUT and a mock")
10class TaskRepositoryMockTest {
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 createMock() {
23 repository = mockk(relaxed = true)
24 }
25
26 @Test
27 @DisplayName("Should ensure that argument fulfills the given predicate")
28 fun shouldEnsureThatArgumentFulfillsPredicate() {
29 repository.create(Task(
30 id = TASK_ID,
31 creator = Creator(id = CREATOR_ID),
32 title = TITLE,
33 status = TaskStatus.OPEN,
34 resolution = null
35 ))
36
37 verify(exactly = 1) { repository.create(match { it.id == TASK_ID }) }
38 }
39}
We can now verify the interactions which happened between the system under test and a mock. Let’s summarize what we learned from this lesson.
Summary
This lesson has taught us five things:
- The
verify()function allows us to verify that the system under test invoked the specified function X times by using the expected function parameters. - The
confirmVerified()function helps us to ensure that our test function verifies all interactions between the system under test and the specified mock objects. - The
verifyAll()function verifies all specified function invocations and ignores the order of function invocations. - The
verifyOrder()function ensures that the specified function invocations happened in the expected order. - The
verifySequence()function verifies that the specified function invocations happened in the expected order and ensures that there are no other interactions between the system under test and our mock.