To be more specific, we are going to work with both verification and capturing– MockK Kotlin features, allowing us to perform assertions against our mocks.
To view other articles in this series, please take a look at:
- Getting Started with MockK in Kotlin [1/5]
- Verification in MockK [2/5]
- MockK: Objects, Top-Level and Extension Functions [3/5]
- MockK: Spies, Relaxed Mocks, and Partial Mocking [4/5]
- MockK with Coroutines [5/5]
Video Content
As always, if you prefer a video content, then please check out my latest YouTube video:
Why Do We Need Verification?
At this point, you may wonder why we need to do anything else besides what we did previously. We configured mocks, and they worked; we made the necessary assertions.
Then what is the point of anything else?
Well, our logic is pretty clear and predictable:
fun createUser(email: String, password: String): UUID { userRepository.findUserByEmail(email) ?.let { throw IllegalArgumentException("User with email $email already exists") } return userRepository.saveUser(email, password) .also { userId -> emailService.sendEmail( to = email, subject = "Welcome to Codersee!", body = "Welcome user: $userId." ) } }
And by predictable, I mean that we see that every function will be invoked at most 1 time. That no other functions are invoked when we throw the exception. And I do not see any possibility that saveUser
somehow would trigger before findUserByEmail
.
But sometimes, that’s not the case.
Sometimes mocks may be invoked a variable number of times depending on some response, right? For example, when we call some user endpoint, we get a list of users, and then we fetch something for each user.
Another time, the order may be affected by the algorithm. And yet another time, it may be simply a spaghetti you inherited from someone else😉
And in a moment, we will learn what exactly we can do in our tests.
Verification with eq
Before we head to the actual functions, let me share a first MockK verification tip: prefer eq
matchers wherever possible.
To better visualize, let’s recap the test example once again:
@Test fun `should throw IllegalArgumentException when user with given e-mail already exists`() { val email = "contact@codersee.com" val password = "pwd" val foundUser = User(UUID.randomUUID(), email, password) every { userRepository.findUserByEmail(email) } returns foundUser assertThrows<IllegalArgumentException> { service.createUser(email, password) } }
We clearly see that foundUser
will be returned only if the findUserByEmail
is invoked with contact@codersee.com
And this is already a kind of verification. Because if we would use any()
, or any<String>()
, the test would still pass. However, it would pass even if our logic sent a password there by mistake!
So I guess you see my point here. For such cases, I would go for eq
matcher.
Various MockK Verifications
And with all of that said, we can finally take a look at various, actual verifications in MockK.
For that purpose, let’s introduce a more dummy example that will help us to focus on the topic without unwanted code:
class A ( private val b: B, private val c: C ) { fun funToTest(): Int { return 5 } } class B { fun funFromB(): Int = 10 } class C { fun funFromC(): String = "Hi!" }
verify
Let’s start with the simplest one- verify
.
And for that, let’s update the funToTest
and write a simple test for it:
fun funToTest(): Int { repeat(10) { b.funFromB() } repeat(5) { c.funFromC() } return 5 } @Test fun `should return 5 without any verification`() { every { b.funFromB() } returns 1 every { c.funFromC() } returns "" val result = a.funToTest() assertEquals(5, result) }
When we run the test, it succeeds. Moreover, we can clearly see that we can define stubbing once. Even though functions are invoked multiple times.
With the verify
, we can make sure that they were invoked a specific number of times:
@Test fun `should return 5 with verification and confirmation`() { every { b.funFromB() } returns 1 every { c.funFromC() } returns "" val result = a.funToTest() assertEquals(5, result) verify(exactly = 10) { b.funFromB() } verify(atLeast = 5) { c.funFromC() } confirmVerified(b, c) }
We can use exactly
, atLeast
, atMost
– the choice is up to you.
Additionally, if we would like to set some arbitraty timeout, then this is our go-to function.
confirmVerified
Moreover, we should use verify
in combination with confirmVerified
.
This way, we additionally check if our test code verified all invoked functions.
If we get rid of verify(atLeast = 5) { c.funFromC() }
and rerun the test, we will see:
Verification acknowledgment failed Verified call count: 0 Recorded call count: 5 Not verified calls: 1) C(#2).funFromC() 2) C(#2).funFromC() 3) C(#2).funFromC() 4) C(#2).funFromC() 5) C(#2).funFromC()
So, as we can see, this is a great way to double-check our test assumptions (testing a test?🙂).
checkUnnecessaryStub
Speaking of testing the test- we can additionally check if there are no unused stubbings.
For that purpose, let’s slightly update the example:
class A( private val b: B, private val c: C ) { fun funToTest(): Int { repeat(10) { b.funFromB(7) } repeat(5) { c.funFromC() } return 5 } } class B { fun funFromB(some: Int): Int = 10 } class C { fun funFromC(): String = "Hi!" }
So this time, funFromB
will always be invoked with 7
.
And if we make our test this way:
@Test fun `should return 5 with verification and confirmation`() { every { b.funFromB(7) } returns 1 every { b.funFromB(6) } returns 2 // unwanted every { c.funFromC() } returns "" val result = a.funToTest() assertEquals(5, result) verify(exactly = 10) { b.funFromB(7) } verify(atLeast = 5) { c.funFromC() } confirmVerified(b, c) checkUnnecessaryStub(b, c) }
Then MockK will be complaining a bit:
1) B(#1).funFromB(eq(6))) java.lang.AssertionError: Unnecessary stubbings detected. Following stubbings are not used, either because there are unnecessary or because tested code doesn't call them : 1) B(#1).funFromB(eq(6)))
verifyCount
When it comes to counts, we can use the verifyCount
function instead:
@Test fun `should return 5 with verifyCount`() { every { b.funFromB(7) } returns 1 every { c.funFromC() } returns "" val result = a.funToTest() assertEquals(5, result) verifyCount { 10 * { b.funFromB(7) } (4..5) * { c.funFromC() } } confirmVerified(b, c) }
This allows us to achieve an even cleaner test with beautiful Kotlin DSL.
Nevertheless, we still have to invoke confirmVerified
separately.
verifyAll/verifyOrder/verifySequence
Sometimes, we can use verifyAll
, verifyOrder
, and verifySequence
to make the code slightly cleaner.
The verifyAll
checks if all calls happened, but it does not verify the order:
@Test fun `should return 5 with verifyAll`() { every { b.funFromB(7) } returns 1 every { c.funFromC() } returns "" val result = a.funToTest() assertEquals(5, result) verifyAll { c.funFromC() b.funFromB(7) } }
So, the above function will work like a charm.
On the other hand, if we use the verifyOrder
, it won’t work anymore, and we will have to provide a valid order:
@Test fun `should return 5 with verifyOrder`() { every { b.funFromB(7) } returns 1 every { c.funFromC() } returns "" val result = a.funToTest() assertEquals(5, result) verifyOrder { b.funFromB(7) c.funFromC() } }
Lastly, the verifySequence
will work similar to verifyOrder
, but it will force us to provide the right amount of times in the right order.
So, to make the test work, we would need to do a small tweak:
@Test fun `should return 5 with verifySequence`() { every { b.funFromB(7) } returns 1 every { c.funFromC() } returns "" val result = a.funToTest() assertEquals(5, result) verifySequence { repeat(10) { b.funFromB(7) } repeat(5) { c.funFromC() } } }
The important thing to mention is that if in our verifyAll
or verifySequence
block we covered all mocks, then we don’t need to add confirmVerified
.
But, unfortunately, this will succeed:
@Test fun `should fail due to missing verification`() { every { b.funFromB(7) } returns 1 every { c.funFromC() } returns "" val result = a.funToTest() assertEquals(5, result) verifySequence { repeat(10) { b.funFromB(7) } } }
And we must guard ourselves with confirmVerified(c)
.
However, if we do the b
and c
check inside. Then the confirmedVerified
is not needed anymore.
MockK capturing
Lastly, I would like to show you one more interesting MockK feature useful in verification- the capturing.
Let’s imagine a new function inside the B
class:
class B { fun anotherFunFromB(some: Int) {} }
Now, the anotherFunToTest
invokes it using the random value:
fun anotherFunToTest(): Int { b.anotherFunFromB(Random.nextInt()) return 3 }
So, if we write a test this way:
@Test fun `should return 3`() { justRun { b.anotherFunFromB(any()) } val result = a.anotherFunToTest() assertEquals(3, result) }
We clearly see, that we cannot access this value in any way, right? It is not returned from the function itself. We cannot control it with other mock, too.
Thankfully, we can introduce a slot and capture the value on the fly:
@Test fun `should return 3`() { val randomSlot = slot<Int>() justRun { b.anotherFunFromB(capture(randomSlot)) } val result = a.anotherFunToTest() println("Random value: ${randomSlot.captured}") assertEquals(3, result) }
This way, the stubbing works just like any()
,but additionally, we can access the random value with captured
.
And although this may not be a clear verification, in some cases, it can be really helpful.
Summary
That’s all for this article about verification in MockK.
We covered various approaches, techniques, and at this point I am pretty sure you will be able to pick the right one for your test cases.
Without any further ado, let’s head to the next article in this series: