From d4f75a7572c1dfaca46006e6e50263535164886a Mon Sep 17 00:00:00 2001 From: Milosz Kukula Date: Thu, 15 Oct 2020 16:30:02 +0200 Subject: [PATCH] #510 | Fix problem with 2 matchers with the same type. --- .../impl/recording/ChainedCallDetector.kt | 6 +- .../recording/SignatureMatcherDetector.kt | 14 +-- .../impl/recording/SignatureMatchersList.kt | 24 +++++ .../test/kotlin/io/mockk/gh/Issue510Test.kt | 41 ++++++++ .../impl/recording/ChainedCallDetectorTest.kt | 6 +- .../recording/SignatureMatchersListTest.kt | 94 +++++++++++++++++++ 6 files changed, 172 insertions(+), 13 deletions(-) create mode 100644 mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureMatchersList.kt create mode 100644 mockk/common/src/test/kotlin/io/mockk/gh/Issue510Test.kt create mode 100644 mockk/common/src/test/kotlin/io/mockk/impl/recording/SignatureMatchersListTest.kt diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/ChainedCallDetector.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/ChainedCallDetector.kt index dddf75630..94207bd5b 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/ChainedCallDetector.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/ChainedCallDetector.kt @@ -19,7 +19,7 @@ class ChainedCallDetector(safeToString: SafeToString) { fun detect( callRounds: List, callN: Int, - matcherMap: HashMap, Matcher<*>> + matcherList: SignatureMatchersList ) { val callInAllRounds = callRounds.map { it.calls[callN] } val zeroCall = callInAllRounds[0] @@ -51,7 +51,7 @@ class ChainedCallDetector(safeToString: SafeToString) { log.trace { "Signature for $nArgument argument of ${zeroCall.method.toStr()}: $signature" } - val matcherBySignature = matcherMap.remove(signature) + val matcherBySignature = matcherList.remove(signature) return buildMatcher( nArgument == 0, @@ -84,7 +84,7 @@ class ChainedCallDetector(safeToString: SafeToString) { log.trace { "Signature for $nArgument/$nVarArg argument of ${zeroCall.method.toStr()}: $signature" } - val matcherBySignature = matcherMap.remove(signature) + val matcherBySignature = matcherList.remove(signature) varArgMatchers.add( buildMatcher( nArgument == 0 && nVarArg == 0, diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureMatcherDetector.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureMatcherDetector.kt index dabbad175..1f124dbb3 100644 --- a/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureMatcherDetector.kt +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureMatcherDetector.kt @@ -45,7 +45,7 @@ class SignatureMatcherDetector( } } - val matcherMap = hashMapOf, Matcher<*>>() + val matcherList = SignatureMatchersList() val allCompositeMatchers = mutableListOf>>() fun gatherMatchers() { @@ -59,10 +59,10 @@ class SignatureMatcherDetector( }) } - matcherMap[signature] = matcher + matcherList.add(signature, matcher) } - log.trace { "Matcher map: $matcherMap" } + log.trace { "Matcher list: $matcherList" } } @Suppress("UNCHECKED_CAST") @@ -77,7 +77,7 @@ class SignatureMatcherDetector( log.trace { "Signature for $nOp operand of $matcher composite matcher: $signature" } - matcherMap.remove(signature) + matcherList.remove(signature) ?: ChainedCallDetector.eqOrNullMatcher(matcher.operandValues[nOp]) } as List>? } @@ -90,13 +90,13 @@ class SignatureMatcherDetector( repeat(nCalls) { callN -> val detector = chainedCallDetectorFactory() - detector.detect(callRounds, callN, matcherMap) + detector.detect(callRounds, callN, matcherList) calls.add(detector.call) } processCompositeMatchers() - if (matcherMap.isNotEmpty()) { - throw MockKException("Failed matching mocking signature for\n${callRounds[0].calls.joinToString("\n")}\nleft matchers: ${matcherMap.values}") + if (matcherList.isNotEmpty()) { + throw MockKException("Failed matching mocking signature for\n${callRounds[0].calls.joinToString("\n")}\nleft matchers: $matcherList") } } } diff --git a/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureMatchersList.kt b/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureMatchersList.kt new file mode 100644 index 000000000..0763ee83b --- /dev/null +++ b/mockk/common/src/main/kotlin/io/mockk/impl/recording/SignatureMatchersList.kt @@ -0,0 +1,24 @@ +package io.mockk.impl.recording + +import io.mockk.Matcher + +class SignatureMatchersList { + private var matchers = mutableListOf() + + fun add(signature: List, matcher: Matcher<*>) { + matchers.add(SignatureWithMatcher(signature, matcher)) + } + + fun remove(signature: List): Matcher<*>? { + val index = matchers.indexOfFirst { it.signature == signature } + return if (index > -1) matchers.removeAt(index).matcher else null + } + + fun isNotEmpty() = matchers.isNotEmpty() + + override fun toString(): String { + return matchers.map { it.matcher }.toString() + } + + private data class SignatureWithMatcher(val signature: List, val matcher: Matcher<*>) +} \ No newline at end of file diff --git a/mockk/common/src/test/kotlin/io/mockk/gh/Issue510Test.kt b/mockk/common/src/test/kotlin/io/mockk/gh/Issue510Test.kt new file mode 100644 index 000000000..20ce38744 --- /dev/null +++ b/mockk/common/src/test/kotlin/io/mockk/gh/Issue510Test.kt @@ -0,0 +1,41 @@ +package io.mockk.gh + +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlin.test.Test + +class Issue510Test { + + class ShopService { + + fun buyProducts(products: List) { + println("You bought...") + } + + fun addProductAndOrders(products: List, orders: List) { + println("Add and orders...") + } + } + + data class Product(val name: String, val price: Int) + data class Order(val name: String) + + @Test + fun `should match with two arguments of type list`() { + // given + val shopService = mockk() + val products = listOf(Product("raspberry", 2), Product("banana", 1)) + val orders = listOf(Order("raspberry"), Order("banana")) + + every { + shopService.addProductAndOrders(products = any(), orders = any()) + } returns Unit + + // when + shopService.addProductAndOrders(products, orders) + + // then + verify { shopService.addProductAndOrders(products, orders) } + } +} diff --git a/mockk/common/src/test/kotlin/io/mockk/impl/recording/ChainedCallDetectorTest.kt b/mockk/common/src/test/kotlin/io/mockk/impl/recording/ChainedCallDetectorTest.kt index 917c34503..585d905fe 100644 --- a/mockk/common/src/test/kotlin/io/mockk/impl/recording/ChainedCallDetectorTest.kt +++ b/mockk/common/src/test/kotlin/io/mockk/impl/recording/ChainedCallDetectorTest.kt @@ -30,16 +30,16 @@ class ChainedCallDetectorTest { every { call1.method.varArgsArg } returns -1 every { call2.method.varArgsArg } returns -1 - detector.detect(listOf(callRound1, callRound2), 0, hashMapOf()) + detector.detect(listOf(callRound1, callRound2), 0, SignatureMatchersList()) assertEquals("abc", detector.call.matcher.method.name) } @Test fun givenTwoCallsRoundsWithOneCallOneArgWhenDetectCallsHappenThenOneCallWithArgIsReturned() { - val matcherMap = hashMapOf, Matcher<*>>() + val matcherMap = SignatureMatchersList() - matcherMap[listOf(signedMatcher1.signature, signedMatcher2.signature)] = signedMatcher1.matcher + matcherMap.add(listOf(signedMatcher1.signature, signedMatcher2.signature), signedMatcher1.matcher) every { callRound1.calls } returns listOf(call1) every { callRound2.calls } returns listOf(call2) diff --git a/mockk/common/src/test/kotlin/io/mockk/impl/recording/SignatureMatchersListTest.kt b/mockk/common/src/test/kotlin/io/mockk/impl/recording/SignatureMatchersListTest.kt new file mode 100644 index 000000000..fb1f4cf06 --- /dev/null +++ b/mockk/common/src/test/kotlin/io/mockk/impl/recording/SignatureMatchersListTest.kt @@ -0,0 +1,94 @@ +package io.mockk.impl.recording + +import io.mockk.AllAnyMatcher +import io.mockk.mockk +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNull +import kotlin.test.assertTrue + +class SignatureMatchersListTest { + + @Test + fun shouldAddMatcherForSignature() { + // given + val map = SignatureMatchersList() + val signedMatcher = mockk(relaxed = true) + val signature = listOf(signedMatcher.signature) + val matcher = signedMatcher.matcher + + // when + map.add(signature, matcher) + map.add(signature, matcher) + + // then + assertTrue { map.isNotEmpty() } + } + + @Test + fun shouldNotContainAnyMatchers() { + // given + val map = SignatureMatchersList() + + // when && then + assertFalse { map.isNotEmpty() } + } + + @Test + fun shouldNotRemoveFromEmptyList() { + // given + val map = SignatureMatchersList() + val signedMatcher = mockk(relaxed = true) + val signature = listOf(signedMatcher.signature) + + // when + val matcher = map.remove(signature) + + // then + assertFalse { map.isNotEmpty() } + assertNull(matcher) + } + + @Test + fun shouldRemoveFromList() { + // given + val map = SignatureMatchersList() + val signedMatcher1 = mockk(relaxed = true) + val signedMatcher2 = mockk(relaxed = true) + val signature1 = listOf(signedMatcher1.signature) + val signature2 = listOf(signedMatcher2.signature) + val matcher1 = signedMatcher1.matcher + val matcher2 = signedMatcher2.matcher + + // when + map.add(signature1, matcher1) + map.add(signature2, matcher2) + + // and + val matcher = map.remove(signature1) + + // then + assertTrue { map.isNotEmpty() } + assertEquals(matcher, matcher1) + } + + @Test + fun shouldPrintListCorrectly() { + // given + val list = SignatureMatchersList() + val signedMatcher = mockk(relaxed = true) + val signature = listOf(signedMatcher.signature) + val matcher = AllAnyMatcher() + val expectedMatchersListAsString = "[$matcher]" + + // when + list.add(signature, matcher) + + // and + val matchersAsString = list.toString() + + // then + assertEquals(matchersAsString, expectedMatchersListAsString) + } +} \ No newline at end of file