Verification in MockK [2/5]

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]

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:

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]

The post Verification in MockK [2/5] appeared first on Codersee — Kotlin on the backend.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *