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.

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 its id as search criteria. If no task is found from the database, this function returns null.
  • 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 atLeast parameter allows us to verify that the specified function was invoked at least X times.
    • The atMost parameter helps us to ensure that the specified function was invoked at most X times.
    • The exactly parameter allows us to specify the exact number of the expected function invocations.
  • 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 the verify() 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.

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.

Get the source code from Github