Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix verifier logic for slots and different matchers #951

Merged
merged 1 commit into from Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -55,15 +55,17 @@ open class UnorderedCallVerifier(
val allCallsForMock = stub.allRecordedCalls()
val allCallsForMockMethod = stub.allRecordedCalls(matcher.method)

if(allCallsForMockMethod.size > 1 && matcher.args.any { it is CapturingSlotMatcher<*> }) {
val matchedCalls = allCallsForMockMethod.filter(matcher::match)

if(matchedCalls.size > 1 && matcher.args.any { it is CapturingSlotMatcher<*> }) {
Comment on lines +58 to +60
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's OK if there are multiple calls overall but only one of them matches and will be captured

val msg = "$matcher execution is being verified more than once and its arguments are being captured with a slot.\n" +
"This will store only the argument of the last invocation in the slot.\n" +
"If you want to store all the arguments, use a mutableList to capture arguments."
throw MockKException(msg)
}

val result = if (min == 0 && max == 0) {
if (!allCallsForMockMethod.any(matcher::match)) {
if (matchedCalls.isEmpty()) {
VerificationResult.OK(listOf())
} else {
VerificationResult.Failure(
Expand Down Expand Up @@ -127,7 +129,6 @@ open class UnorderedCallVerifier(
}
}
else -> {
val matchedCalls = allCallsForMockMethod.filter(matcher::match)
val n = matchedCalls.count()
if (n in min..max) {
VerificationResult.OK(matchedCalls)
Expand Down Expand Up @@ -162,7 +163,7 @@ open class UnorderedCallVerifier(
}

captureBlocks.add {
for (call in allCallsForMockMethod) {
for (call in matchedCalls) {
Copy link
Contributor Author

@m-burst m-burst Oct 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

captureAnswer was called for non-matched calls, which led to incorrect capturing results

matcher.captureAnswer(call)
}
}
Expand Down
@@ -0,0 +1,80 @@
package io.mockk.it

import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
import io.mockk.verifyOrder
import io.mockk.verifySequence
import kotlin.test.Test
import kotlin.test.assertIs
import kotlin.test.assertTrue

/**
* See issue #774.
*/
class CaptureSubclassVerificationTest {

interface Interface

class Subclass1 : Interface

class Subclass2 : Interface

interface Service {
fun method(obj: Interface)
}

@Test
fun `test unordered`() {
val service = mockk<Service> {
every { method(any()) } just Runs
}

service.method(Subclass1())
service.method(Subclass2())

val slot = slot<Subclass2>()
verify(exactly = 1) { service.method(capture(slot)) }
assertTrue(slot.isCaptured)
assertIs<Subclass2>(slot.captured)
}

@Test
fun `test ordered`() {
val service = mockk<Service> {
every { method(any()) } just Runs
}

service.method(Subclass1())
service.method(Subclass2())

val slot = slot<Subclass2>()
verifyOrder {
service.method(any())
service.method(capture(slot))
}
assertTrue(slot.isCaptured)
assertIs<Subclass2>(slot.captured)
}

@Test
fun `test sequence`() {
val service = mockk<Service> {
every { method(any()) } just Runs
}

service.method(Subclass1())
service.method(Subclass2())

val slot = slot<Subclass2>()
verifySequence {
service.method(any())
service.method(capture(slot))
}
assertTrue(slot.isCaptured)
assertIs<Subclass2>(slot.captured)
}
}
23 changes: 21 additions & 2 deletions modules/mockk/src/commonTest/kotlin/io/mockk/it/CapturingTest.kt
Expand Up @@ -85,8 +85,8 @@ class CapturingTest {

assertFailsWith<MockKException> {
verify {
mock.doSomething("1", capture(dataSlotId1))
mock.doSomething("2", capture(dataSlotId2))
Comment on lines -88 to -89
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would not fail if left as is, because with different values of the first argument only 1 call will be matched in each case

mock.doSomething(any(), capture(dataSlotId1))
mock.doSomething(any(), capture(dataSlotId2))
}
}
}
Expand Down Expand Up @@ -136,6 +136,8 @@ class CapturingTest {
mock.doSomething("2", capture(slotList))
}

// Each capture should have happened once because of different matchers for `id` argument
assertEquals(slotList.size, 2)
Comment on lines +139 to +140
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this was 4

assertEquals("data1", slotList[0])
assertEquals("data2", slotList[1])
}
Expand All @@ -158,6 +160,23 @@ class CapturingTest {
assertEquals(args, list)
}

@Test
fun itDoesNotThrowAMockkExceptionWhenVerifyingTheSameFunctionTwiceWithSlotsWithDifferentMatchers() {
mock.doSomething("1", "data1")
mock.doSomething("2", "data2")

val dataSlotId1 = slot<String>()
val dataSlotId2 = slot<String>()

verify {
mock.doSomething("1", capture(dataSlotId1))
mock.doSomething("2", capture(dataSlotId2))
}

assertEquals("data1", dataSlotId1.captured)
assertEquals("data2", dataSlotId2.captured)
}

class MockNullableCls {
fun call(unused: String?) {
println(unused)
Expand Down