Introduction to MockK, Part Two

After we have finished this webinar, we:

  • Know what argument matching is.
  • Understand how we can use the different argument matchers provided by MockK.

Get the source code from Github

A Very Quick Introduction to Argument Matching

Argument matching is a technique which allows us to verify that the actual argument which is passed to the invoked function of our test double fulfills the specified condition. We can use argument matching when we stub functions of our test double or verify interactions that happened between the system under test and our test double.

Next, we will take a look at the system under test.

Introduction to System Under Test

During this webinar we will use argument matchers when we stub the functions of a repository that handles 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}

Let’s move on and find out how we can use the argument matchers provided by MockK.

Using the Argument Matchers Provided by MockK

Now we will take a look at twelve examples which demonstrate how we can use the argument matchers provided by MockK. Let’s begin.

Example 1: The Default Matcher

If we don’t specify the used argument matcher, MockK uses the matcher returned by the eq() function. This matcher ensures that the actual argument is equal to the specified value or object.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the value of the id argument is equal to 1L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use the default matcher")
39    fun shouldMatchSimpleFunctionArgumentWithDefaultMatcher() {
40        every { repository.findById(ID) } returns TASK
41
42        val returned = repository.findById(ID)
43        assertThat(returned?.id).isEqualByComparingTo(ID)
44    }
45}
Also, I want to point out that even though the MockK user’s manual claims that the default matcher can be used only for simple values, I tested this with Task objects and I found out that MockK uses the matcher returned by the eq() function in this case as well.

Example 2: The Argument Is Equal to the Specified Value

If we want to ensure that the actual argument is equal to the specified value or object, we have to use the matcher returned by the eq() function.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the value of the id argument is equal to 1L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use eq()")
39    fun shouldMatchFunctionArgumentWithEq() {
40        every { repository.findById(eq(ID)) } returns TASK
41
42        val returned = repository.findById(ID)
43        assertThat(returned?.id).isEqualByComparingTo(ID)
44    }
45}

Example 3: The Argument Isn’t Equal to the Provided Value

If we want to ensure that the actual argument isn’t equal to the specified value or object, we have to use the matcher returned by the neq() function.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the value of the id argument isn’t equal to -1L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use neq()")
39    fun shouldMatchFunctionArgumentWithNeq() {
40        every { repository.findById(neq(-1L)) } returns TASK
41
42        val returned = repository.findById(ID)
43        assertThat(returned?.id).isEqualByComparingTo(ID)
44    }
45}
Before we continue, I want to point that the eq() and neq() functions use the deepEquals() function. If you want to compare referential equality, you must use the refEq() and nrefEq() functions. On the other hand, if you want to compare the equality of two values or objects by using the compareTo() function, you must create your argument matcher by using the cmpEq() function.

Example 4: The Argument Is Less Than the Specified Value

If we want to ensure that the actual argument is less than the specified value, we have to use the matcher returned by the less() function.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the value of the id argument is less than 2L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use less()")
39    fun shouldMatchFunctionArgumentWithLess() {
40        every { repository.findById(less(ID + 1)) } returns TASK
41
42        val returned = repository.findById(ID)
43        assertThat(returned?.id).isEqualByComparingTo(ID)
44    }
45}

Example 5: The Argument Is Less Than Or Equal to the Provided Value

If we want to ensure that the actual argument is less than or equal to the specified value, we have to use the matcher returned by the less() function. When we invoke this function, we have to configure the “expected” value and ensure that the value of the andEquals argument is true.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the value of the id argument is less than or equal to 1L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("should use less() when andEquals is true")
39    fun shouldMatchFunctionArgumentWithLessAndEqual() {
40        every { repository.findById(less(
41                value = ID,
42                andEquals = true
43        )) } returns TASK
44
45        val returned = repository.findById(ID)
46        assertThat(returned?.id).isEqualByComparingTo(ID)
47    }
48}

Example 6: The Argument Is Greater Than the Specified Value

If we want to ensure that the actual argument is greater than the specified value, we have to use the matcher returned by the more() function.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the value of the id argument is greater than 0L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use more()")
39    fun shouldMatchFunctionArgumentWithMore() {
40        every { repository.findById(more(ID - 1)) } returns TASK
41
42        val returned = repository.findById(ID)
43        assertThat(returned?.id).isEqualByComparingTo(ID)
44    }
45}

Example 7: The Argument Is Greater Than Or Equal to the Specified Value

If we want to ensure that the actual argument is greater than or equal to the specified value, we have to use the matcher returned by the more() function. When we invoke this function, we have to configure the “expected” value and ensure that the value of the andEquals argument is true.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the value of the id argument is greater than or equal to 1L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use more() when andEquals is true")
39    fun shouldMatchFunctionArgumentWithMoreAndEqual() {
40        every { repository.findById(more(
41                value = ID,
42                andEquals = true
43        )) } returns TASK
44
45        val returned = repository.findById(ID)
46        assertThat(returned?.id).isEqualByComparingTo(ID)
47    }
48}
Before we continue, I want to point that the four previous argument matchers use the compareTo() function.

Example 8: The Argument Is Any Object

If we want to ensure that the actual argument is any object, we have to use the matcher returned by the any() function.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the actual id argument is any object, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use any()")
39    fun shouldMatchFunctionArgumentWithAny() {
40        every { repository.findById(any()) } returns TASK
41
42        val returned = repository.findById(ID)
43        assertThat(returned?.id).isEqualByComparingTo(ID)
44    }
45}

Example 9: The Argument Is a Long Object

If we want to ensure that the type of the actual argument is correct, we have to use the matcher returned by the ofType() function. When we invoke this function, we must specify the expected type.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the actual id argument is a Long object, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use ofType()")
39    fun shouldMatchFunctionArgumentWithOfType() {
40        every { repository.findById(ofType(Long::class)) } returns TASK
41
42        val returned = repository.findById(ID)
43        assertThat(returned?.id).isEqualByComparingTo(ID)
44    }
45}

Example 10: The Argument Fulfills the Given Predicate

If we want to ensure that the actual argument fulfills the specified predicate, we have to use the matcher returned by the match() function. When we invoke this function, we must specify the predicate that must be true.

For example, if we want to ensure that the findById() function returns the found task when it’s invoked and the actual id argument is equal to 1L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByIdStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use match()")
39    fun shouldMatchFunctionArgumentWithMatch() {
40        every { repository.findById(match { it == ID }) } returns TASK
41
42        val returned = repository.findById(ID)
43        assertThat(returned?.id).isEqualByComparingTo(ID)
44    }
45}

Example 11: A Complex Argument Fulfills the Given Predicate

If we want to ensure that the actual argument has a property which is equal to the specified value, we have to verify that the actual argument fulfills a predicate. In other words, we can use the matcher returned by the match() function.

For example, if we want to ensure that the create() function returns the created task when it’s invoked and the id of the created task is equal to 1L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one complex function parameter")
11class CreateStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Should use match()")
39    fun shouldMatchFunctionArgumentWithMatch() {
40        every { repository.create(match { it.id == ID }) } returns TASK
41
42        val returned = repository.create(TASK)
43        assertThat(returned).isSameAs(TASK)
44    }
45}

Example 12: Mix Argument Values With Argument Matchers

If the invoked function has multiple function parameters, we can also mix argument values with argument matchers. For example, if we want to ensure that the findByCreatorAndStatus() function returns the found tasks when it’s invoked and the user of this function wants to get closed tasks which were created by a user whose user id is 5L, the source code of our test class looks as follows:

 1import io.mockk.every
 2import io.mockk.mockk
 3import org.assertj.core.api.Assertions.assertThat
 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("Stub function which has one simple function parameter")
11class FindByCreatorAndStatusStubExampleTest {
12
13    companion object {
14
15        private const val ID = 1L
16        private const val CREATOR_ID = 5L
17        private const val TITLE = "Write stub example"
18        private val STATUS = TaskStatus.CLOSED
19        private val RESOLUTION = TaskResolution.DONE
20
21        private val TASK = Task(
22                id = ID,
23                creator = Creator(id = CREATOR_ID),
24                title = TITLE,
25                status = STATUS,
26                resolution = RESOLUTION
27        )
28    }
29
30    private lateinit var repository: TaskRepository
31
32    @BeforeEach
33    fun createRepositoryStub() {
34        repository = mockk<TaskRepository>()
35    }
36
37    @Test
38    @DisplayName("Mix regular arguments and argument matchers")
39    fun shouldMatchArgumentsWithDefaultMatcherAndProvidedMatcher() {
40        every { repository.findByCreatorAndStatus(
41                CREATOR_ID,
42                eq(STATUS)
43        ) } returns listOf(TASK)
44
45        val returned = repository.findByCreatorAndStatus(CREATOR_ID, STATUS)
46        assertThat(returned).hasSize(1)
47    }
48}

We can now use the argument matchers provided by MockK. Let’s summarize what we learned from this webinar.

Summary

This webinar has taught us eight things:

  • If we don’t specify the used argument matcher, MockK uses the matcher returned by the eq() function.
  • If we want to use an argument matcher which uses the deepEquals() function, we have to create our argument matcher by using the eq() or neq() functions.
  • If we want to compare the referential equality of two objects, we must create our argument matcher by using the refEq() or nrefEq() functions.
  • If we want to use an argument matcher which uses the compareTo() function, we have to create our argument matcher by using the cmpEq(), less(), or more() functions.
  • If we want to ensure that the actual argument value is any object, we have to create our argument matcher by using the any() function.
  • If we want to ensure that the type of the actual argument is correct, we have to create our argument matcher by using the ofType() function.
  • If we want to verify that the actual argument fulfills a predicate, we must create our argument matcher by using the match() function.
  • If the invoked function has multiple function parameters, we can mix actual argument values with argument matchers.

Get the source code from Github