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.
This webinar assumes that:
- You can run Kotlin tests with Maven or Gradle
- You can write test classes with JUnit 5 and Kotlin
- You can create test doubles with 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 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}
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}
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}
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}
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}
Additional Reading:
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 theeq()orneq()functions. - If we want to compare the referential equality of two objects, we must create our argument matcher by using the
refEq()ornrefEq()functions. - If we want to use an argument matcher which uses the
compareTo()function, we have to create our argument matcher by using thecmpEq(),less(), ormore()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.