diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 9f549a94..806afb6b 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -39,7 +39,7 @@ toc::[] * `allowInterfaces`, which combines `allowInterfacesOnKmock` and `allowInterfacesOnKspy` * `disableFactories` in order to disable the generation of `kmock` and `kspy` if needed * `customAnnotationsForMeta` to provide a hook for the usage of customized annotation for meta/shared sources -* `assertOrder` in order to make the names more consistent +* `assertOrder` in order to make the names more consistent and preserves the old behaviour of `verifyStrictOrder` === Changed @@ -48,11 +48,12 @@ toc::[] * `kmock` and `kspy` are using now a shared function to improve compile time * Non intrusive behaviour (spy & relaxation) is now resolved by proxy invocation rather then by proxy initialisation in order to cover edge cases * Assertion-/VerificationChain is not coupled any longer directly to proxies and provide improved error messages +* Expectation-methods do not bleed into the global context any longer +* `verifyStrictOrder` is now used for total order of certain Proxies but allows partial order between different Proxies === Deprecated * `uselessPrefixes` in the Gradle Extension -* `verifyStrictOrder` use `assertOrder` instead === Removed diff --git a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoSpyFactorySpec.kt b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoSpyFactorySpec.kt index a2e72616..7c2589ef 100644 --- a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoSpyFactorySpec.kt +++ b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoSpyFactorySpec.kt @@ -25,12 +25,8 @@ import tech.antibytes.kmock.example.contract.SampleRemoteRepositoryMock import tech.antibytes.kmock.verification.Asserter import tech.antibytes.kmock.verification.NonFreezingAsserter import tech.antibytes.kmock.verification.assertOrder -import tech.antibytes.kmock.verification.hasBeenCalledWith -import tech.antibytes.kmock.verification.hasBeenCalledWithout -import tech.antibytes.kmock.verification.hasBeenStrictlyCalledWith import tech.antibytes.kmock.verification.verify import tech.antibytes.kmock.verification.verifyOrder -import tech.antibytes.kmock.verification.wasSetTo import tech.antibytes.util.test.coroutine.AsyncTestReturnValue import tech.antibytes.util.test.coroutine.clearBlockingTest import tech.antibytes.util.test.coroutine.defaultTestContext diff --git a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubFactorySpec.kt b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubFactorySpec.kt index f31b43f8..c8718fc3 100644 --- a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubFactorySpec.kt +++ b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubFactorySpec.kt @@ -21,12 +21,8 @@ import tech.antibytes.kmock.example.contract.SampleLocalRepositoryMock import tech.antibytes.kmock.example.contract.SampleRemoteRepositoryMock import tech.antibytes.kmock.verification.Asserter import tech.antibytes.kmock.verification.assertOrder -import tech.antibytes.kmock.verification.hasBeenCalledWith -import tech.antibytes.kmock.verification.hasBeenCalledWithout -import tech.antibytes.kmock.verification.hasBeenStrictlyCalledWith import tech.antibytes.kmock.verification.verify import tech.antibytes.kmock.verification.verifyOrder -import tech.antibytes.kmock.verification.wasSetTo import tech.antibytes.util.test.coroutine.AsyncTestReturnValue import tech.antibytes.util.test.coroutine.clearBlockingTest import tech.antibytes.util.test.coroutine.defaultTestContext diff --git a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubRelaxedSpec.kt b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubRelaxedSpec.kt index e0ebff3d..850dedc5 100644 --- a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubRelaxedSpec.kt +++ b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubRelaxedSpec.kt @@ -22,13 +22,8 @@ import tech.antibytes.kmock.example.contract.SampleLocalRepositoryMock import tech.antibytes.kmock.example.contract.SampleRemoteRepositoryMock import tech.antibytes.kmock.verification.Asserter import tech.antibytes.kmock.verification.assertOrder -import tech.antibytes.kmock.verification.hasBeenCalledWith -import tech.antibytes.kmock.verification.hasBeenCalledWithout -import tech.antibytes.kmock.verification.hasBeenStrictlyCalledWith import tech.antibytes.kmock.verification.verify import tech.antibytes.kmock.verification.verifyOrder -import tech.antibytes.kmock.verification.wasSet -import tech.antibytes.kmock.verification.wasSetTo import tech.antibytes.util.test.coroutine.AsyncTestReturnValue import tech.antibytes.util.test.coroutine.clearBlockingTest import tech.antibytes.util.test.coroutine.defaultTestContext diff --git a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubSpec.kt b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubSpec.kt index d74614b3..44ef89aa 100644 --- a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubSpec.kt +++ b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubSpec.kt @@ -21,12 +21,8 @@ import tech.antibytes.kmock.example.contract.SampleLocalRepositoryMock import tech.antibytes.kmock.example.contract.SampleRemoteRepositoryMock import tech.antibytes.kmock.verification.Asserter import tech.antibytes.kmock.verification.assertOrder -import tech.antibytes.kmock.verification.hasBeenCalledWith -import tech.antibytes.kmock.verification.hasBeenCalledWithout -import tech.antibytes.kmock.verification.hasBeenStrictlyCalledWith import tech.antibytes.kmock.verification.verify import tech.antibytes.kmock.verification.verifyOrder -import tech.antibytes.kmock.verification.wasSetTo import tech.antibytes.util.test.coroutine.AsyncTestReturnValue import tech.antibytes.util.test.coroutine.clearBlockingTest import tech.antibytes.util.test.coroutine.defaultTestContext diff --git a/examples/src/jsTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubFactoryJsSpec.kt b/examples/src/jsTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubFactoryJsSpec.kt index de1f24dd..311b4e4f 100644 --- a/examples/src/jsTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubFactoryJsSpec.kt +++ b/examples/src/jsTest/kotlin/tech/antibytes/kmock/example/SampleControllerAutoStubFactoryJsSpec.kt @@ -21,12 +21,8 @@ import tech.antibytes.kmock.example.contract.SampleLocalRepositoryMock import tech.antibytes.kmock.example.contract.SampleRemoteRepositoryMock import tech.antibytes.kmock.verification.Asserter import tech.antibytes.kmock.verification.assertOrder -import tech.antibytes.kmock.verification.hasBeenCalledWith -import tech.antibytes.kmock.verification.hasBeenCalledWithout -import tech.antibytes.kmock.verification.hasBeenStrictlyCalledWith import tech.antibytes.kmock.verification.verify import tech.antibytes.kmock.verification.verifyOrder -import tech.antibytes.kmock.verification.wasSetTo import tech.antibytes.util.test.coroutine.AsyncTestReturnValue import tech.antibytes.util.test.coroutine.clearBlockingTest import tech.antibytes.util.test.coroutine.defaultTestContext diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt index dfa0d194..cfd347d3 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt @@ -1253,25 +1253,21 @@ object KMockContract { /** * Asserts that a FunProxy was called with n-parameter. - * The arguments do not need to be complete. - * @param arguments the expected arguments. + * @param arguments or constraints which calls must match. The arguments/constraints must follow the order of the mocked/stubbed function but can contain gaps and do not need to all arguments. * @throws AssertionError if the assertion fails */ fun FunProxy<*, *>.hasBeenCalledWith(vararg arguments: Any?) /** * Asserts that a FunProxy was called with n-parameter. - * The arguments must be complete. - * @param arguments the expected arguments. + * @param arguments or constraints which calls must match. The arguments/constraints must follow the order of the mocked/stubbed function and need to contain all arguments/constraints. * @throws AssertionError if the assertion fails */ fun FunProxy<*, *>.hasBeenStrictlyCalledWith(vararg arguments: Any?) /** * Asserts that a FunProxy was without called n-parameter. - * The arguments do not need to be complete. - * @param proxy the actual proxy. - * @param illegal the forbidden arguments. + * @param illegal arguments or constraints which calls is not allowed not match. * @throws AssertionError if the assertion fails */ fun FunProxy<*, *>.hasBeenCalledWithout(vararg illegal: Any?) @@ -1296,13 +1292,73 @@ object KMockContract { fun PropertyProxy<*>.wasSetTo(value: Any?) } + /** + * Provider for Verification. + * @author Matthias Geisler + */ + interface VerificationContext { + /** + * Collects all invocation of a FunProxy. + * @return Expectation + */ + fun FunProxy<*, *>.hasBeenCalled(): Expectation + + /** + * Collects all invocation of a FunProxy which contain no Arguments. + * @return Expectation + */ + fun FunProxy<*, *>.hasBeenCalledWithVoid(): Expectation + + /** + * Collects all invocation of an FunProxy which matches the given Arguments. + * @param arguments or constraints which calls must match. The arguments/constraints must follow the order of the mocked/stubbed function but can contain gaps and do not need to all arguments. + * @return Expectation + * @see ArgumentConstraint + */ + fun FunProxy<*, *>.hasBeenCalledWith(vararg arguments: Any?): Expectation + + /** + * Collects all invocation of an FunProxy which matches the given Arguments. + * @param arguments or constraints which calls must match. The arguments/constraints must follow the order of the mocked/stubbed function and need to contain all arguments/constraints. + * @return Expectation + * @see ArgumentConstraint + */ + fun FunProxy<*, *>.hasBeenStrictlyCalledWith(vararg arguments: Any?): Expectation + + /** + * Collects all invocation of an FunProxy which matches the given Arguments. + * @param illegal arguments or constraints which calls is not allowed not match. + * @return Expectation + * @see ArgumentConstraint + */ + fun FunProxy<*, *>.hasBeenCalledWithout(vararg illegal: Any?): Expectation + + /** + * Collects all invocation of an PropertyProxy Getter. + */ + fun PropertyProxy<*>.wasGotten(): Expectation + + /** + * Collects all invocation of an PropertyProxy Setter. + */ + fun PropertyProxy<*>.wasSet(): Expectation + + /** + * Collects all invocation of an PropertyProxy Setter with the given Value. + * @return Expectation + * @param value argument/constraint which calls must match. + * @see ArgumentConstraint + */ + fun PropertyProxy<*>.wasSetTo(value: Any?): Expectation + } + /** * Insurance that given Proxies are covered by the AssertionChain. * @author Matthias Geisler */ fun interface AssertionInsurance { /** - * Ensures that given Proxies are covered by the AssertionChain. Use this method with caution! + * Ensures that given Proxies are covered by the AssertionChain. * @throws IllegalStateException if a given Proxy is not covered by a AssertionChain. */ fun ensureVerificationOf(vararg proxies: Proxy<*, *>) @@ -1351,6 +1407,7 @@ object KMockContract { internal const val MISSING_INVOCATION = "Expected %0th call of %1 was not made." internal const val MISMATCH = "Expected <%0> got actual <%1>." + internal const val CALL_WITH_ARGS_NOT_FOUND = "Expected %0 to be invoked with %1, but no matching call was found." internal const val HAD_BEEN_CALLED_NO_MATCHER = "The given matcher %0 has not been found." internal const val MISMATCHING_SIZE = "Expected <%0> arguments got actual <%1>." internal const val ILLEGAL_VALUE = "Illegal value <%0> detected." @@ -1363,5 +1420,5 @@ object KMockContract { internal const val TOO_LESS_CALLS = "Expected at least %0 calls, but found only %1." internal const val TOO_MANY_CALLS = "Expected at most %0 calls, but exceeded with %1." - internal const val NOT_PART_OF_CHAIN = "The given proxy %0 is not part of this AssertionChain." + internal const val NOT_PART_OF_CHAIN = "The given proxy %0 is not part of this chain." } diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Asserter.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Asserter.kt index c9de3890..c75fd0b9 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Asserter.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Asserter.kt @@ -22,6 +22,7 @@ class Asserter(coverAllInvocations: Boolean = false) : AsserterBase(coverAllInvo } /** - * + * Alias to Asserter. + * @author Matthias Geisler */ typealias Verifier = Asserter diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/ExpectationFactory.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/ExpectationFactory.kt deleted file mode 100644 index 80d18eee..00000000 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/ExpectationFactory.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2022 Matthias Geisler (bitPogo) / All rights reserved. - * - * Use of this source code is governed by Apache v2.0 - */ - -package tech.antibytes.kmock.verification - -import tech.antibytes.kmock.KMockContract.ArgumentConstraint -import tech.antibytes.kmock.KMockContract.FunProxy -import tech.antibytes.kmock.KMockContract.PropertyProxy -import tech.antibytes.kmock.KMockContract.Proxy - -private fun traverseMockAndShare( - proxy: Proxy<*, T>, - action: T.() -> Boolean -): Expectation { - val callIndices = mutableListOf() - - for (idx in 0 until proxy.calls) { - if (action(proxy.getArgumentsForCall(idx))) { - callIndices.add(idx) - } - } - - return Expectation(proxy, callIndices) -} - -/** - * VerificationHandle Factory, which collects all invocation of an FunProxy. - * @return VerificationHandle - * @author Matthias Geisler - */ -fun FunProxy<*, *>.hasBeenCalled(): Expectation = traverseMockAndShare(this) { hasBeenCalledWith() } - -/** - * VerificationHandle Factory, which collects all invocation of an FunProxy which contain no Arguments. - * @return VerificationHandle - * @author Matthias Geisler - */ -fun FunProxy<*, *>.hasBeenCalledWithVoid(): Expectation = traverseMockAndShare(this) { hasBeenCalledWithVoid() } - -/** - * VerificationHandle Factory, which collects all invocation of an FunProxy which matches the given Arguments. - * @param arguments or constraints which calls must match. The arguments/constraints must follow the order of the mocked/stubbed function but can contain gaps and do not need to all arguments. - * @return VerificationHandle - * @see ArgumentConstraint - * @author Matthias Geisler - */ -fun FunProxy<*, *>.hasBeenCalledWith( - vararg arguments: Any? -): Expectation = traverseMockAndShare(this) { hasBeenCalledWith(*arguments) } - -/** - * VerificationHandle Factory, which collects all invocation of an FunProxy which matches the given Arguments. - * @param arguments or constraints which calls must match. The arguments/constraints must follow the order of the mocked/stubbed function and need to contain all arguments/constraints. - * @return VerificationHandle - * @see ArgumentConstraint - * @author Matthias Geisler - */ -fun FunProxy<*, *>.hasBeenStrictlyCalledWith( - vararg arguments: Any? -): Expectation = traverseMockAndShare(this) { hasBeenStrictlyCalledWith(*arguments) } - -/** - * VerificationHandle Factory, which collects all invocation of an FunProxy which matches the given Arguments. - * @param illegal arguments/constraints which calls does not match. - * @return VerificationHandle - * @see ArgumentConstraint - * @author Matthias Geisler - */ -fun FunProxy<*, *>.hasBeenCalledWithout( - vararg illegal: Any? -): Expectation = traverseMockAndShare(this) { hasBeenCalledWithout(*illegal) } - -/** - * VerificationHandle Factory, which collects all invocation of an PropertyProxy Getter. - * @return VerificationHandle - * @author Matthias Geisler - */ -fun PropertyProxy<*>.wasGotten(): Expectation = traverseMockAndShare(this) { wasGotten() } - -/** - * VerificationHandle Factory, which collects all invocation of an PropertyProxy Setter. - * @return VerificationHandle - * @author Matthias Geisler - */ -fun PropertyProxy<*>.wasSet(): Expectation = traverseMockAndShare(this) { wasSet() } - -/** - * VerificationHandle Factory, which collects all invocation of an PropertyProxy Setter with the given Value. - * @return VerificationHandle - * @param value argument/constraint which calls must match. - * @see ArgumentConstraint - * @author Matthias Geisler - */ -fun PropertyProxy<*>.wasSetTo( - value: Any? -): Expectation = traverseMockAndShare(this) { wasSetTo(value) } diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/NonFreezingAsserter.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/NonFreezingAsserter.kt index 0b90d53b..b48b6210 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/NonFreezingAsserter.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/NonFreezingAsserter.kt @@ -20,6 +20,7 @@ class NonFreezingAsserter(coverAllInvocations: Boolean = false) : AsserterBase(c } /** - * + * Alias to NonFreezingAsserter. + * @author Matthias Geisler */ typealias NonFreezingVerifier = NonFreezingAsserter diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/ProxyAssertion.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/ProxyAssertion.kt index 6b3a367c..d94849fe 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/ProxyAssertion.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/ProxyAssertion.kt @@ -23,8 +23,9 @@ import tech.antibytes.kmock.KMockExperimental fun FunProxy<*, *>.assertHasBeenCalled( exactly: Int, ) { + val proxy = this verify(exactly = exactly) { - this.hasBeenCalledWith() + proxy.hasBeenCalledWith() } } @@ -40,8 +41,9 @@ fun FunProxy<*, *>.assertHasBeenCalled( fun FunProxy<*, *>.assertHasBeenCalledWithVoid( exactly: Int, ) { + val proxy = this verify(exactly = exactly) { - this.hasBeenCalledWithVoid() + proxy.hasBeenCalledWithVoid() } } @@ -60,8 +62,9 @@ fun FunProxy<*, *>.assertHasBeenCalledWith( exactly: Int, vararg arguments: Any? ) { + val proxy = this verify(exactly = exactly) { - this.hasBeenCalledWith(*arguments) + proxy.hasBeenCalledWith(*arguments) } } @@ -80,8 +83,9 @@ fun FunProxy<*, *>.assertHasBeenCalledStrictlyWith( exactly: Int, vararg arguments: Any? ) { + val proxy = this verify(exactly = exactly) { - this.hasBeenStrictlyCalledWith(*arguments) + proxy.hasBeenStrictlyCalledWith(*arguments) } } @@ -94,8 +98,9 @@ fun FunProxy<*, *>.assertHasBeenCalledStrictlyWith( */ @KMockExperimental fun FunProxy<*, *>.assertHasNotBeenCalled() { + val proxy = this verify(exactly = 0) { - this.hasBeenCalledWith() + proxy.hasBeenCalledWith() } } @@ -112,8 +117,9 @@ fun FunProxy<*, *>.assertHasNotBeenCalled() { fun FunProxy<*, *>.assertHasBeenCalledWithout( vararg illegal: Any? ) { + val proxy = this verify(atMost = Int.MAX_VALUE) { - this.hasBeenCalledWithout(*illegal) + proxy.hasBeenCalledWithout(*illegal) } } @@ -127,7 +133,8 @@ fun FunProxy<*, *>.assertHasBeenCalledWithout( */ @KMockExperimental fun KMockContract.PropertyProxy<*>.assertWasGotten(exactly: Int) { - verify(exactly = exactly) { this.wasGotten() } + val proxy = this + verify(exactly = exactly) { proxy.wasGotten() } } /** @@ -140,7 +147,8 @@ fun KMockContract.PropertyProxy<*>.assertWasGotten(exactly: Int) { */ @KMockExperimental fun KMockContract.PropertyProxy<*>.assertWasSet(exactly: Int) { - verify(exactly = exactly) { this.wasSet() } + val proxy = this + verify(exactly = exactly) { proxy.wasSet() } } /** @@ -155,5 +163,6 @@ fun KMockContract.PropertyProxy<*>.assertWasSet(exactly: Int) { */ @KMockExperimental fun KMockContract.PropertyProxy<*>.assertWasSetTo(exactly: Int, value: Any?) { - verify(exactly = exactly) { this.wasSetTo(value) } + val proxy = this + verify(exactly = exactly) { proxy.wasSetTo(value) } } diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/StrictVerificationChain.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/StrictVerificationChain.kt new file mode 100644 index 00000000..d7b6ad88 --- /dev/null +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/StrictVerificationChain.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022 Matthias Geisler (bitPogo) / All rights reserved. + * + * Use of this source code is governed by Apache v2.0 + */ + +package tech.antibytes.kmock.verification + +import kotlinx.atomicfu.atomic +import kotlinx.atomicfu.update +import tech.antibytes.kmock.KMockContract.AssertionChain +import tech.antibytes.kmock.KMockContract.Assertions +import tech.antibytes.kmock.KMockContract.CALL_NOT_FOUND +import tech.antibytes.kmock.KMockContract.ChainedAssertion +import tech.antibytes.kmock.KMockContract.FunProxy +import tech.antibytes.kmock.KMockContract.NOT_PART_OF_CHAIN +import tech.antibytes.kmock.KMockContract.PropertyProxy +import tech.antibytes.kmock.KMockContract.Proxy +import tech.antibytes.kmock.KMockContract.Reference +import tech.antibytes.kmock.util.format + +internal class StrictVerificationChain( + private val references: List, + private val assertions: Assertions = Assertions, +) : AssertionChain, ChainedAssertion { + private val invocation = atomic(0) + + private fun getProxyIdSet(): Set { + val set: MutableSet = mutableSetOf() + + references.forEach { reference -> set.add(reference.proxy.id) } + + return set + } + + override fun ensureVerificationOf(vararg proxies: Proxy<*, *>) { + val actual = getProxyIdSet() + + proxies.forEach { proxy -> + if (proxy.id !in actual) { + throw IllegalStateException(NOT_PART_OF_CHAIN.format(proxy.id)) + } + } + } + + private fun findProxy(expected: Proxy<*, *>): Int? { + for (idx in invocation.value until references.size) { + if (references[idx].proxy === expected) { + return idx + } + } + + return null + } + + private fun runAssertion( + expected: Proxy<*, *>, + action: (callIndex: Int) -> Unit + ) { + val actual = findProxy(expected) + ?: throw AssertionError(CALL_NOT_FOUND.format(expected.id)) + + val expectedCallIdxReference = references[actual].callIndex + + action(expectedCallIdxReference) + + invocation.update { actual + 1 } + } + + override fun FunProxy<*, *>.hasBeenCalled() { + runAssertion(this) { callIndex -> + assertions.hasBeenCalledAtIndex(this, callIndex) + } + } + + override fun FunProxy<*, *>.hasBeenCalledWithVoid() { + runAssertion(this) { callIndex -> + assertions.hasBeenCalledWithVoidAtIndex(this, callIndex) + } + } + + override fun FunProxy<*, *>.hasBeenCalledWith(vararg arguments: Any?) { + runAssertion(this) { callIndex -> + assertions.hasBeenCalledWithAtIndex( + proxy = this, + callIndex = callIndex, + arguments = arguments + ) + } + } + + override fun FunProxy<*, *>.hasBeenStrictlyCalledWith(vararg arguments: Any?) { + runAssertion(this) { callIndex -> + assertions.hasBeenStrictlyCalledWithAtIndex( + proxy = this, + callIndex = callIndex, + arguments = arguments + ) + } + } + + override fun FunProxy<*, *>.hasBeenCalledWithout(vararg illegal: Any?) { + runAssertion(this) { callIndex -> + assertions.hasBeenCalledWithoutAtIndex( + proxy = this, + callIndex = callIndex, + illegal = illegal + ) + } + } + + override fun PropertyProxy<*>.wasGotten() { + runAssertion(this) { callIndex -> + assertions.wasGottenAtIndex(proxy = this, callIndex = callIndex) + } + } + + override fun PropertyProxy<*>.wasSet() { + runAssertion(this) { callIndex -> + assertions.wasSetAtIndex(proxy = this, callIndex = callIndex) + } + } + + override fun PropertyProxy<*>.wasSetTo(value: Any?) { + runAssertion(this) { callIndex -> + assertions.wasSetToAtIndex( + proxy = this, + callIndex = callIndex, + value = value + ) + } + } + + @Throws(AssertionError::class) + override fun ensureAllReferencesAreEvaluated() = Unit +} diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt index 7ded9b9d..345da5cf 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt @@ -9,7 +9,6 @@ package tech.antibytes.kmock.verification import tech.antibytes.kmock.KMockContract import tech.antibytes.kmock.KMockContract.Asserter import tech.antibytes.kmock.KMockContract.AssertionContext -import tech.antibytes.kmock.KMockContract.AssertionInsurance import tech.antibytes.kmock.KMockContract.ChainedAssertion import tech.antibytes.kmock.KMockContract.Expectation import tech.antibytes.kmock.KMockContract.NOT_CALLED @@ -52,23 +51,16 @@ private infix fun Expectation.mustBeAtMost(value: Int) { * @param atMost optional parameter which indicates the maximum amount of calls. * @param action producer of a VerificationHandle. * @throws AssertionError if given criteria are not met. - * @see hasBeenCalled - * @see hasBeenCalledWithVoid - * @see hasBeenCalledWith - * @see hasBeenStrictlyCalledWith - * @see hasBeenCalledWithout - * @see wasGotten - * @see wasSet - * @see wasSetTo + * @see KMockContract.VerificationContext * @author Matthias Geisler */ fun verify( exactly: Int? = null, atLeast: Int = 1, atMost: Int? = null, - action: () -> Expectation + action: KMockContract.VerificationContext.() -> Expectation ) { - val handle = action() + val handle = action(VerificationContext) val minimum = exactly ?: atLeast val maximum = exactly ?: atMost @@ -81,9 +73,9 @@ fun verify( fun runAssertion( chain: T, - scope: ChainedAssertion.() -> Any + action: ChainedAssertion.() -> Any ) where T : ChainedAssertion, T : KMockContract.AssertionChain { - scope(chain) + action(chain) chain.ensureAllReferencesAreEvaluated() } @@ -91,32 +83,30 @@ fun runAssertion( /** * Asserts a chain of Expectations. Each Expectations must be in strict order of the referenced Proxy invocations * and all invocations must be present. -* @param scope chain of Expectation Methods. +* @param action chain of Expectation Methods. * @throws AssertionError if given criteria are not met. * @see AssertionContext * @author Matthias Geisler */ -fun Asserter.assertOrder(scope: ChainedAssertion.() -> Any) = runAssertion(AssertionChain(references), scope) +fun Asserter.assertOrder(action: ChainedAssertion.() -> Any) = runAssertion(AssertionChain(references), action) /** - * Alias of assertOrder. - * @param scope chain of Expectation Methods. + * Verifies a chain of Expectations. Expectation between different proxies can contain gaps. + * Also the chain does not need to be exhaustive. + * @param action chain of Expectation Methods. * @throws AssertionError if given criteria are not met. - * @see assertOrder * @author Matthias Geisler */ -@Deprecated( - "This will be removed with version 1.0. Use assertOrder instead.", - ReplaceWith("assertOrder(scope)") -) -fun Asserter.verifyStrictOrder(scope: ChainedAssertion.() -> Any) = assertOrder(scope) +fun Asserter.verifyStrictOrder(action: ChainedAssertion.() -> Any) { + runAssertion(StrictVerificationChain(references), action) +} /** * Verifies a chain of Expectations. Each Expectations must be in order but gaps are allowed. * Also the chain does not need to be exhaustive. - * @param scope chain of Expectation Methods. + * @param action chain of Expectation Methods. * @throws AssertionError if given criteria are not met. * @see AssertionContext * @author Matthias Geisler */ -fun Asserter.verifyOrder(scope: AssertionInsurance.() -> Any) = runAssertion(VerificationChain(references), scope) +fun Asserter.verifyOrder(action: ChainedAssertion.() -> Any) = runAssertion(VerificationChain(references), action) diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt index 74d7dade..3a0c798d 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt @@ -8,20 +8,24 @@ package tech.antibytes.kmock.verification import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update +import tech.antibytes.kmock.KMockContract import tech.antibytes.kmock.KMockContract.AssertionChain -import tech.antibytes.kmock.KMockContract.Assertions import tech.antibytes.kmock.KMockContract.CALL_NOT_FOUND +import tech.antibytes.kmock.KMockContract.CALL_WITH_ARGS_NOT_FOUND import tech.antibytes.kmock.KMockContract.ChainedAssertion import tech.antibytes.kmock.KMockContract.FunProxy +import tech.antibytes.kmock.KMockContract.ILLEGAL_VALUE +import tech.antibytes.kmock.KMockContract.MISSING_INVOCATION +import tech.antibytes.kmock.KMockContract.NOT_GET import tech.antibytes.kmock.KMockContract.NOT_PART_OF_CHAIN +import tech.antibytes.kmock.KMockContract.NOT_SET import tech.antibytes.kmock.KMockContract.PropertyProxy import tech.antibytes.kmock.KMockContract.Proxy -import tech.antibytes.kmock.KMockContract.Reference +import tech.antibytes.kmock.KMockContract.VOID_FUNCTION import tech.antibytes.kmock.util.format internal class VerificationChain( - private val references: List, - private val assertions: Assertions = Assertions, + private val references: List, ) : AssertionChain, ChainedAssertion { private val invocation = atomic(0) @@ -62,72 +66,133 @@ internal class VerificationChain( val expectedCallIdxReference = references[actual].callIndex - action(expectedCallIdxReference) - invocation.update { actual + 1 } + + action(expectedCallIdxReference) } - override fun FunProxy<*, *>.hasBeenCalled() { - runAssertion(this) { callIndex -> - assertions.hasBeenCalledAtIndex(this, callIndex) + private fun Proxy<*, T>.guardActualRetrieval(callIndex: Int): T { + return try { + this.getArgumentsForCall(callIndex) + } catch (_: Throwable) { + throw AssertionError(MISSING_INVOCATION.format(callIndex + 1, this.id)) } } + private fun mapValues(vararg values: Any?): List = values.map { it } + + override fun FunProxy<*, *>.hasBeenCalled() = runAssertion(this) { callIdx -> guardActualRetrieval(callIdx) } + override fun FunProxy<*, *>.hasBeenCalledWithVoid() { runAssertion(this) { callIndex -> - assertions.hasBeenCalledWithVoidAtIndex(this, callIndex) + val valid = this.guardActualRetrieval(callIndex).hasBeenCalledWithVoid() + + if (!valid) { + try { + this.hasBeenCalledWithVoid() + } catch (e: AssertionError) { + throw AssertionError(VOID_FUNCTION.format(this.id)) + } + } } } override fun FunProxy<*, *>.hasBeenCalledWith(vararg arguments: Any?) { runAssertion(this) { callIndex -> - assertions.hasBeenCalledWithAtIndex( - proxy = this, - callIndex = callIndex, - arguments = arguments - ) + val valid = this.guardActualRetrieval(callIndex).hasBeenCalledWith(*arguments) + + if (!valid) { + try { + this.hasBeenCalledWith(*arguments) + } catch (e: AssertionError) { + throw AssertionError( + CALL_WITH_ARGS_NOT_FOUND.format( + this.id, + mapValues(*arguments) + ) + ) + } + } } } override fun FunProxy<*, *>.hasBeenStrictlyCalledWith(vararg arguments: Any?) { runAssertion(this) { callIndex -> - assertions.hasBeenStrictlyCalledWithAtIndex( - proxy = this, - callIndex = callIndex, - arguments = arguments - ) + val valid = this.guardActualRetrieval(callIndex).hasBeenStrictlyCalledWith(*arguments) + + if (!valid) { + try { + this.hasBeenStrictlyCalledWith(*arguments) + } catch (e: AssertionError) { + throw AssertionError( + CALL_WITH_ARGS_NOT_FOUND.format( + this.id, + mapValues(*arguments) + ) + ) + } + } } } override fun FunProxy<*, *>.hasBeenCalledWithout(vararg illegal: Any?) { runAssertion(this) { callIndex -> - assertions.hasBeenCalledWithoutAtIndex( - proxy = this, - callIndex = callIndex, - illegal = illegal - ) + val valid = this.guardActualRetrieval(callIndex).hasBeenCalledWithout(*illegal) + + if (!valid) { + try { + this.hasBeenCalledWithout(*illegal) + } catch (e: AssertionError) { + throw AssertionError(ILLEGAL_VALUE.format(mapValues(*illegal))) + } + } } } override fun PropertyProxy<*>.wasGotten() { runAssertion(this) { callIndex -> - assertions.wasGottenAtIndex(proxy = this, callIndex = callIndex) + val valid = this.guardActualRetrieval(callIndex).wasGotten() + + if (!valid) { + try { + this.wasGotten() + } catch (e: AssertionError) { + throw AssertionError(NOT_GET) + } + } } } override fun PropertyProxy<*>.wasSet() { runAssertion(this) { callIndex -> - assertions.wasSetAtIndex(proxy = this, callIndex = callIndex) + val valid = this.guardActualRetrieval(callIndex).wasSet() + + if (!valid) { + try { + this.wasSet() + } catch (e: AssertionError) { + throw AssertionError(NOT_SET) + } + } } } override fun PropertyProxy<*>.wasSetTo(value: Any?) { runAssertion(this) { callIndex -> - assertions.wasSetToAtIndex( - proxy = this, - callIndex = callIndex, - value = value - ) + val valid = this.guardActualRetrieval(callIndex).wasSetTo(value) + + if (!valid) { + try { + this.wasSetTo(value) + } catch (e: AssertionError) { + throw AssertionError( + CALL_WITH_ARGS_NOT_FOUND.format( + this.id, + value + ) + ) + } + } } } diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationContext.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationContext.kt new file mode 100644 index 00000000..fcff7eda --- /dev/null +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationContext.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Matthias Geisler (bitPogo) / All rights reserved. + * + * Use of this source code is governed by Apache v2.0 + */ + +package tech.antibytes.kmock.verification + +import tech.antibytes.kmock.KMockContract +import tech.antibytes.kmock.KMockContract.FunProxy +import tech.antibytes.kmock.KMockContract.PropertyProxy +import tech.antibytes.kmock.KMockContract.Proxy + +internal object VerificationContext : KMockContract.VerificationContext { + private fun traverseMock( + proxy: Proxy<*, T>, + action: T.() -> Boolean + ): Expectation { + val callIndices = mutableListOf() + + for (idx in 0 until proxy.calls) { + if (action(proxy.getArgumentsForCall(idx))) { + callIndices.add(idx) + } + } + + return Expectation(proxy, callIndices) + } + + override fun FunProxy<*, *>.hasBeenCalled(): Expectation = traverseMock(this) { hasBeenCalledWith() } + + override fun FunProxy<*, *>.hasBeenCalledWithVoid(): Expectation = traverseMock(this) { hasBeenCalledWithVoid() } + + override fun FunProxy<*, *>.hasBeenCalledWith( + vararg arguments: Any? + ): Expectation = traverseMock(this) { hasBeenCalledWith(*arguments) } + + override fun FunProxy<*, *>.hasBeenStrictlyCalledWith( + vararg arguments: Any? + ): Expectation = traverseMock(this) { hasBeenStrictlyCalledWith(*arguments) } + + override fun FunProxy<*, *>.hasBeenCalledWithout( + vararg illegal: Any? + ): Expectation = traverseMock(this) { hasBeenCalledWithout(*illegal) } + + override fun PropertyProxy<*>.wasGotten(): Expectation = traverseMock(this) { wasGotten() } + + override fun PropertyProxy<*>.wasSet(): Expectation = traverseMock(this) { wasSet() } + + override fun PropertyProxy<*>.wasSetTo( + value: Any? + ): Expectation = traverseMock(this) { wasSetTo(value) } +} diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/AssertionChainSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/AssertionChainSpec.kt index 34ba0137..08b1fe7b 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/AssertionChainSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/AssertionChainSpec.kt @@ -101,7 +101,7 @@ class AssertionChainSpec { } // Then - error.message mustBe "The given proxy ${proxy.id} is not part of this AssertionChain." + error.message mustBe "The given proxy ${proxy.id} is not part of this chain." } @Test diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/AssertionsSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/AssertionsSpec.kt index 9da256be..b1af1a6a 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/AssertionsSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/AssertionsSpec.kt @@ -172,14 +172,14 @@ class AssertionsSpec { @Test @JsName("fn10") - fun `Given hasBeenCalledWithAtIndex is called it accepts if the invocation if the expected and actual values match`() { + fun `Given hasBeenCalledWithAtIndex is called it accepts if the invocation if the expected and actual values match without beeing in linear order`() { // Given val id: String = fixture.fixture() val proxy = fixture.funProxyFixture(id = id) val expectedValue: String = fixture.fixture() - proxy.getArgumentsForCall = { arrayOf(expectedValue) } + proxy.getArgumentsForCall = { arrayOf(fixture.fixture(), expectedValue) } // When Assertions.hasBeenCalledWithAtIndex(proxy, 0, expectedValue) diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt new file mode 100644 index 00000000..b34fe752 --- /dev/null +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt @@ -0,0 +1,831 @@ +/* + * Copyright (c) 2022 Matthias Geisler (bitPogo) / All rights reserved. + * + * Use of this source code is governed by Apache v2.0 + */ + +package tech.antibytes.kmock.verification + +import tech.antibytes.kmock.KMockContract +import tech.antibytes.kmock.KMockContract.Proxy +import tech.antibytes.kmock.KMockContract.Reference +import tech.antibytes.kmock.fixture.funProxyFixture +import tech.antibytes.kmock.fixture.propertyProxyFixture +import tech.antibytes.mock.AssertionsStub +import tech.antibytes.util.test.fixture.fixture +import tech.antibytes.util.test.fixture.kotlinFixture +import tech.antibytes.util.test.fixture.listFixture +import tech.antibytes.util.test.fulfils +import tech.antibytes.util.test.mustBe +import tech.antibytes.util.test.sameAs +import kotlin.js.JsName +import kotlin.test.Test +import kotlin.test.assertFailsWith + +class StrictVerificationChainSpec { + private val fixture = kotlinFixture() + + @Test + @JsName("fn0") + fun `It fulfils AssertionChain`() { + StrictVerificationChain(emptyList()) fulfils KMockContract.AssertionChain::class + } + + @Test + @JsName("fn1") + fun `It fulfils ChainedAssertion`() { + StrictVerificationChain(emptyList()) fulfils KMockContract.ChainedAssertion::class + } + + @Test + @JsName("fn2") + fun `Given ensureAllReferencesAreEvaluated is called it accepts allways`() { + // Given + val proxy1 = fixture.funProxyFixture() + val proxy2 = fixture.funProxyFixture() + val references = listOf( + Reference(proxy1, fixture.fixture()), + Reference(proxy2, fixture.fixture()), + Reference(proxy1, fixture.fixture()), + Reference(proxy2, fixture.fixture()), + ) + + val chain = StrictVerificationChain(references) + + // When + chain.ensureAllReferencesAreEvaluated() + } + + @Test + @JsName("fn3") + fun `Given ensureVerification it fails if the given Proxy is not part of it`() { + // Given + val proxy = fixture.funProxyFixture() + + // When + val container = StrictVerificationChain(emptyList()) + + val error = assertFailsWith { + container.ensureVerificationOf(proxy) + } + + // Then + error.message mustBe "The given proxy ${proxy.id} is not part of this chain." + } + + @Test + @JsName("fn4") + fun `Given ensureVerification it accepts if the given Proxy is part of it`() { + // Given + val proxy = fixture.funProxyFixture() + val container = StrictVerificationChain( + listOf( + Reference(proxy, 0) + ) + ) + + // When + container.ensureVerificationOf(proxy) + } + + private fun invoke( + chain: StrictVerificationChain, + action: StrictVerificationChain.() -> Unit + ) = action(chain) + + @Test + @JsName("fn5") + fun `Given hasBeenCalled is called in a Chain it fails if the current References are exhausted`() { + // Given + val id: String = fixture.fixture() + val proxy = fixture.funProxyFixture(id = id) + val container = StrictVerificationChain(emptyList()) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + proxy.hasBeenCalled() + } + } + + actual.message mustBe "Expected $id to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn6") + fun `Given hasBeenCalled is called in a Chain it fails if the expected Proxies was not found`() { + // Given + val id1: String = fixture.fixture() + val id2: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id1) + val actualProxy = fixture.funProxyFixture(id = id2) + val references = listOf( + Reference(actualProxy, 0), + ) + + val container = StrictVerificationChain(references) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenCalled() + } + } + + actual.message mustBe "Expected $id1 to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn7") + fun `Given hasBeenCalled is called in a Chain it delegates the call to the given Assertions`() { + // Given + val expectedProxy = fixture.funProxyFixture() + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + var capturedProxy: Proxy<*, *>? = null + var capturedIdx: Int? = null + + val assertions = AssertionsStub( + hasBeenCalledAtIndex = { givenProxy, givenIdx -> + capturedProxy = givenProxy + capturedIdx = givenIdx + } + ) + + val container = StrictVerificationChain(references, assertions) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalled() + } + + capturedProxy sameAs expectedProxy + capturedIdx mustBe callIdx + } + + @Test + @JsName("fn8") + fun `Given hasBeenCalledWithVoid is called in a Chain it fails if the current References are exhausted`() { + // Given + val id: String = fixture.fixture() + val proxy = fixture.funProxyFixture(id = id) + val container = StrictVerificationChain(emptyList()) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + proxy.hasBeenCalledWithVoid() + } + } + + actual.message mustBe "Expected $id to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn9") + fun `Given hasBeenCalledWithVoid is called in a Chain it fails if the expected Proxies was not found`() { + // Given + val id1: String = fixture.fixture() + val id2: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id1) + val actualProxy = fixture.funProxyFixture(id = id2) + val references = listOf( + Reference(actualProxy, 0), + ) + + val container = StrictVerificationChain(references) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenCalledWithVoid() + } + } + + actual.message mustBe "Expected $id1 to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn10") + fun `Given hasBeenCalledWithVoid is called in a Chain it the delegates the call to the Assertions`() { + // Given + val expectedProxy = fixture.funProxyFixture() + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + var capturedProxy: Proxy<*, *>? = null + var capturedIdx: Int? = null + + val assertions = AssertionsStub( + hasBeenCalledWithVoidAtIndex = { givenProxy, givenIdx -> + capturedProxy = givenProxy + capturedIdx = givenIdx + } + ) + + val container = StrictVerificationChain(references, assertions) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalledWithVoid() + } + + capturedProxy sameAs expectedProxy + capturedIdx mustBe callIdx + } + + @Test + @JsName("fn11") + fun `Given hasBeenCalledWith is called in a Chain it fails if the current References are exhausted`() { + // Given + val id: String = fixture.fixture() + val proxy = fixture.funProxyFixture(id = id) + val container = StrictVerificationChain(emptyList()) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + proxy.hasBeenCalledWith(fixture.fixture()) + } + } + + actual.message mustBe "Expected $id to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn12") + fun `Given hasBeenCalledWith is called in a Chain it fails if the expected Proxies was not found`() { + // Given + val id1: String = fixture.fixture() + val id2: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id1) + val actualProxy = fixture.funProxyFixture(id = id2) + val references = listOf( + Reference(actualProxy, 0), + ) + + val container = StrictVerificationChain(references) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenCalledWith(fixture.fixture()) + } + } + + actual.message mustBe "Expected $id1 to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn13") + fun `Given hasBeenCalledWith is called in a Chain it delegates the call to the assertions`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + val expectedValue: String = fixture.fixture() + + var capturedProxy: Proxy<*, *>? = null + var capturedIdx: Int? = null + var capturedArguments: Array? = null + + val assertions = AssertionsStub( + hasBeenCalledWithAtIndex = { givenProxy, givenIdx, givenArguments -> + capturedProxy = givenProxy + capturedIdx = givenIdx + capturedArguments = givenArguments + } + ) + + val container = StrictVerificationChain(references, assertions) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalledWith(expectedValue) + } + + capturedProxy sameAs expectedProxy + capturedIdx mustBe callIdx + capturedArguments.contentDeepEquals(arrayOf(expectedValue)) mustBe true + } + + @Test + @JsName("fn14") + fun `Given hasBeenStrictlyCalledWith is called in a Chain it fails if the current References are exhausted`() { + // Given + val id: String = fixture.fixture() + val proxy = fixture.funProxyFixture(id = id) + val container = StrictVerificationChain(emptyList()) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + proxy.hasBeenStrictlyCalledWith(fixture.fixture()) + } + } + + actual.message mustBe "Expected $id to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn15") + fun `Given hasBeenStrictlyCalledWith is called in a Chain it fails if the expected Proxies was not found`() { + // Given + val id1: String = fixture.fixture() + val id2: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id1) + val actualProxy = fixture.funProxyFixture(id = id2) + val references = listOf( + Reference(actualProxy, 0), + ) + + val container = StrictVerificationChain(references) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenStrictlyCalledWith(fixture.fixture()) + } + } + + actual.message mustBe "Expected $id1 to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn16") + fun `Given hasBeenStrictlyCalledWith is called in a Chain it delegates the call to the assertions`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + val expectedValue: String = fixture.fixture() + + var capturedProxy: Proxy<*, *>? = null + var capturedIdx: Int? = null + var capturedArguments: Array? = null + + val assertions = AssertionsStub( + hasBeenStrictlyCalledWithAtIndex = { givenProxy, givenIdx, givenArguments -> + capturedProxy = givenProxy + capturedIdx = givenIdx + capturedArguments = givenArguments + } + ) + + val container = StrictVerificationChain(references, assertions) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenStrictlyCalledWith(expectedValue) + } + + capturedProxy sameAs expectedProxy + capturedIdx mustBe callIdx + capturedArguments.contentDeepEquals(arrayOf(expectedValue)) mustBe true + } + + @Test + @JsName("fn17") + fun `Given hasBeenCalledWithout is called in a Chain it fails if the current References are exhausted`() { + // Given + val id: String = fixture.fixture() + val proxy = fixture.funProxyFixture(id = id) + val container = StrictVerificationChain(emptyList()) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + proxy.hasBeenCalledWithout(fixture.fixture()) + } + } + + actual.message mustBe "Expected $id to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn18") + fun `Given hasBeenCalledWithout is called in a Chain it fails if the expected Proxies was not found`() { + // Given + val id1: String = fixture.fixture() + val id2: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id1) + val actualProxy = fixture.funProxyFixture(id = id2) + val references = listOf( + Reference(actualProxy, 0), + ) + + val container = StrictVerificationChain(references) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenCalledWithout(fixture.fixture()) + } + } + + actual.message mustBe "Expected $id1 to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn19") + fun `Given hasBeenCalledWithout is called in a Chain it delegates the call to the assertions`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + val expectedValue: String = fixture.fixture() + + var capturedProxy: Proxy<*, *>? = null + var capturedIdx: Int? = null + var capturedArguments: Array? = null + + val assertions = AssertionsStub( + hasBeenCalledWithoutAtIndex = { givenProxy, givenIdx, givenArguments -> + capturedProxy = givenProxy + capturedIdx = givenIdx + capturedArguments = givenArguments + } + ) + + val container = StrictVerificationChain(references, assertions) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalledWithout(expectedValue) + } + + capturedProxy sameAs expectedProxy + capturedIdx mustBe callIdx + capturedArguments.contentDeepEquals(arrayOf(expectedValue)) mustBe true + } + + @Test + @JsName("fn20") + fun `Given wasGotten is called in a Chain it fails if the current References are exhausted`() { + // Given + val id: String = fixture.fixture() + val proxy = fixture.propertyProxyFixture(id = id) + val container = StrictVerificationChain(emptyList()) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + proxy.wasGotten() + } + } + + actual.message mustBe "Expected $id to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn21") + fun `Given wasGotten is called in a Chain it fails if the expected Proxies was not found`() { + // Given + val id1: String = fixture.fixture() + val id2: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id1) + val actualProxy = fixture.propertyProxyFixture(id = id2) + val references = listOf( + Reference(actualProxy, 0), + ) + + val container = StrictVerificationChain(references) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + expectedProxy.wasGotten() + } + } + + actual.message mustBe "Expected $id1 to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn22") + fun `Given wasGotten is called in a Chain it delegates the call to the assertions`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + var capturedProxy: Proxy<*, *>? = null + var capturedIdx: Int? = null + + val assertions = AssertionsStub( + wasGottenAtIndex = { givenProxy, givenIdx -> + capturedProxy = givenProxy + capturedIdx = givenIdx + } + ) + + val container = StrictVerificationChain(references, assertions) + + // Then + invoke(container) { + // When + expectedProxy.wasGotten() + } + + capturedProxy sameAs expectedProxy + capturedIdx mustBe callIdx + } + + @Test + @JsName("fn23") + fun `Given wasSet is called in a Chain it fails if the current References are exhausted`() { + // Given + val id: String = fixture.fixture() + val proxy = fixture.propertyProxyFixture(id = id) + val container = StrictVerificationChain(emptyList()) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + proxy.wasSet() + } + } + + actual.message mustBe "Expected $id to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn24") + fun `Given wasSet is called in a Chain it fails if the expected Proxies was not found`() { + // Given + val id1: String = fixture.fixture() + val id2: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id1) + val actualProxy = fixture.propertyProxyFixture(id = id2) + val references = listOf( + Reference(actualProxy, 0), + ) + + val container = StrictVerificationChain(references) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + expectedProxy.wasSet() + } + } + + actual.message mustBe "Expected $id1 to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn25") + fun `Given wasSet is called in a Chain it it delegates the call to the assertions`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + var capturedProxy: Proxy<*, *>? = null + var capturedIdx: Int? = null + + val assertions = AssertionsStub( + wasSetAtIndex = { givenProxy, givenIdx -> + capturedProxy = givenProxy + capturedIdx = givenIdx + } + ) + + val container = StrictVerificationChain(references, assertions) + + // Then + invoke(container) { + // When + expectedProxy.wasSet() + } + + capturedProxy sameAs expectedProxy + capturedIdx mustBe callIdx + } + + @Test + @JsName("fn26") + fun `Given wasSetTo is called in a Chain it fails if the current References are exhausted`() { + // Given + val id: String = fixture.fixture() + val proxy = fixture.propertyProxyFixture(id = id) + val container = StrictVerificationChain(emptyList()) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + proxy.wasSetTo(fixture.fixture()) + } + } + + actual.message mustBe "Expected $id to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn27") + fun `Given wasSetTo is called in a Chain it fails if the expected Proxies was not found`() { + // Given + val id1: String = fixture.fixture() + val id2: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id1) + val actualProxy = fixture.propertyProxyFixture(id = id2) + val references = listOf( + Reference(actualProxy, 0), + ) + + val container = StrictVerificationChain(references) + + // Then + val actual = assertFailsWith { + invoke(container) { + // When + expectedProxy.wasSetTo(fixture.fixture()) + } + } + + actual.message mustBe "Expected $id1 to be invoked, but no further calls were captured." + } + + @Test + @JsName("fn28") + fun `Given wasSetTo is called in a Chain it delegates the call to the assertions`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + val expectedValue: Any? = fixture.fixture() + + var capturedProxy: Proxy<*, *>? = null + var capturedIdx: Int? = null + var capturedValue: Any? = null + + val assertions = AssertionsStub( + wasSetToAtIndex = { givenProxy, givenIdx, argument -> + capturedProxy = givenProxy + capturedIdx = givenIdx + capturedValue = argument + } + ) + + val container = StrictVerificationChain(references, assertions) + + // Then + invoke(container) { + // When + expectedProxy.wasSetTo(expectedValue) + } + + capturedProxy sameAs expectedProxy + capturedIdx mustBe callIdx + capturedValue sameAs expectedValue + } + + @Test + @JsName("fn29") + fun `It respects the pratial order of the chain`() { + // Given + val proxy1 = fixture.funProxyFixture() + val proxy2 = fixture.funProxyFixture() + val proxy3 = fixture.propertyProxyFixture() + + val expectedProxies = listOf( + proxy1, + proxy3, + proxy2, + proxy1, + proxy3, + proxy2, + proxy3, + proxy1, + ) + + val expectedCallIndices: List = fixture.listFixture(size = 8) + + val references = listOf( + Reference(proxy3, fixture.fixture()), + Reference(proxy3, fixture.fixture()), + Reference(proxy3, fixture.fixture()), + Reference(proxy3, fixture.fixture()), + Reference(proxy3, fixture.fixture()), + Reference(proxy3, fixture.fixture()), + Reference(proxy3, fixture.fixture()), + Reference(proxy1, expectedCallIndices[0]), + Reference(proxy3, expectedCallIndices[1]), + Reference(proxy2, expectedCallIndices[2]), + Reference(proxy2, fixture.fixture()), + Reference(proxy2, fixture.fixture()), + Reference(proxy1, expectedCallIndices[3]), + Reference(proxy1, fixture.fixture()), + Reference(proxy1, fixture.fixture()), + Reference(proxy1, fixture.fixture()), + Reference(proxy3, expectedCallIndices[4]), + Reference(proxy2, expectedCallIndices[5]), + Reference(proxy3, expectedCallIndices[6]), + Reference(proxy3, fixture.fixture()), + Reference(proxy3, fixture.fixture()), + Reference(proxy3, fixture.fixture()), + Reference(proxy1, expectedCallIndices[7]), + ) + + val capturedProxies: MutableList> = mutableListOf() + val capturedCallIdx: MutableList = mutableListOf() + + val assertions = AssertionsStub( + hasBeenCalledAtIndex = { givenProxy, givenIdx -> + capturedProxies.add(givenProxy) + capturedCallIdx.add(givenIdx) + }, + hasBeenCalledWithVoidAtIndex = { givenProxy, givenIdx -> + capturedProxies.add(givenProxy) + capturedCallIdx.add(givenIdx) + }, + hasBeenCalledWithAtIndex = { givenProxy, givenIdx, _ -> + capturedProxies.add(givenProxy) + capturedCallIdx.add(givenIdx) + }, + hasBeenStrictlyCalledWithAtIndex = { givenProxy, givenIdx, _ -> + capturedProxies.add(givenProxy) + capturedCallIdx.add(givenIdx) + }, + hasBeenCalledWithoutAtIndex = { givenProxy, givenIdx, _ -> + capturedProxies.add(givenProxy) + capturedCallIdx.add(givenIdx) + }, + wasGottenAtIndex = { givenProxy, givenIdx -> + capturedProxies.add(givenProxy) + capturedCallIdx.add(givenIdx) + }, + wasSetAtIndex = { givenProxy, givenIdx -> + capturedProxies.add(givenProxy) + capturedCallIdx.add(givenIdx) + }, + wasSetToAtIndex = { givenProxy, givenIdx, _ -> + capturedProxies.add(givenProxy) + capturedCallIdx.add(givenIdx) + } + ) + + val chain = StrictVerificationChain(references, assertions) + + // When + invoke(chain) { + proxy1.hasBeenCalled() + proxy3.wasGotten() + proxy2.hasBeenCalledWithVoid() + proxy1.hasBeenCalledWith(fixture.fixture()) + proxy3.wasSet() + proxy2.hasBeenStrictlyCalledWith(fixture.fixture()) + proxy3.wasSetTo(fixture.fixture()) + proxy1.hasBeenCalledWithout(fixture.fixture()) + } + + // Then + capturedProxies mustBe expectedProxies + capturedCallIdx mustBe expectedCallIndices + } +} diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt index 451e9c02..8e609c9a 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt @@ -7,18 +7,14 @@ package tech.antibytes.kmock.verification import tech.antibytes.kmock.KMockContract -import tech.antibytes.kmock.KMockContract.Proxy +import tech.antibytes.kmock.KMockContract.GetOrSet import tech.antibytes.kmock.KMockContract.Reference -import tech.antibytes.kmock.fixture.fixtureVerificationHandle import tech.antibytes.kmock.fixture.funProxyFixture import tech.antibytes.kmock.fixture.propertyProxyFixture -import tech.antibytes.mock.AssertionsStub import tech.antibytes.util.test.fixture.fixture import tech.antibytes.util.test.fixture.kotlinFixture -import tech.antibytes.util.test.fixture.listFixture import tech.antibytes.util.test.fulfils import tech.antibytes.util.test.mustBe -import tech.antibytes.util.test.sameAs import kotlin.js.JsName import kotlin.test.Test import kotlin.test.assertFailsWith @@ -28,31 +24,27 @@ class VerificationChainSpec { @Test @JsName("fn0") - fun `It fulfils VerificationChain`() { + fun `It fulfils AssertionChain`() { VerificationChain(emptyList()) fulfils KMockContract.AssertionChain::class } @Test @JsName("fn1") fun `It fulfils ChainedAssertion`() { - AssertionChain(emptyList()) fulfils KMockContract.ChainedAssertion::class + VerificationChain(emptyList()) fulfils KMockContract.ChainedAssertion::class } @Test @JsName("fn2") - fun `Given ensureAllReferencesAreEvaluated is calledit accepts allways`() { + fun `Given ensureAllReferencesAreEvaluated is called it accepts allways`() { // Given - val handle1 = fixture.fixtureVerificationHandle( - callIndices = listOf(0, 1) - ) - val handle2 = fixture.fixtureVerificationHandle( - callIndices = listOf(0, 1) - ) + val proxy1 = fixture.funProxyFixture() + val proxy2 = fixture.funProxyFixture() val references = listOf( - Reference(handle1.proxy, 0), - Reference(handle2.proxy, 0), - Reference(handle1.proxy, 1), - Reference(handle2.proxy, 1), + Reference(proxy1, fixture.fixture()), + Reference(proxy2, fixture.fixture()), + Reference(proxy1, fixture.fixture()), + Reference(proxy2, fixture.fixture()), ) val chain = VerificationChain(references) @@ -75,7 +67,7 @@ class VerificationChainSpec { } // Then - error.message mustBe "The given proxy ${proxy.id} is not part of this AssertionChain." + error.message mustBe "The given proxy ${proxy.id} is not part of this chain." } @Test @@ -83,7 +75,7 @@ class VerificationChainSpec { fun `Given ensureVerification it accepts if the given Proxy is part of it`() { // Given val proxy = fixture.funProxyFixture() - val container = AssertionChain( + val container = VerificationChain( listOf( Reference(proxy, 0) ) @@ -151,26 +143,16 @@ class VerificationChainSpec { val references = listOf( Reference(expectedProxy, callIdx), ) - var capturedProxy: Proxy<*, *>? = null - var capturedIdx: Int? = null - val assertions = AssertionsStub( - hasBeenCalledAtIndex = { givenProxy, givenIdx -> - capturedProxy = givenProxy - capturedIdx = givenIdx - } - ) + expectedProxy.getArgumentsForCall = { arrayOf() } - val container = VerificationChain(references, assertions) + val container = VerificationChain(references) // Then invoke(container) { // When expectedProxy.hasBeenCalled() } - - capturedProxy sameAs expectedProxy - capturedIdx mustBe callIdx } @Test @@ -219,37 +201,52 @@ class VerificationChainSpec { @Test @JsName("fn10") - fun `Given hasBeenCalledWithVoid is called in a Chain it the delegates the call to the Assertions`() { + fun `Given hasBeenCalledWithVoid is called in a Chain it fails if the given proxy is not void`() { // Given val expectedProxy = fixture.funProxyFixture() val callIdx: Int = fixture.fixture() val references = listOf( Reference(expectedProxy, callIdx), ) - var capturedProxy: Proxy<*, *>? = null - var capturedIdx: Int? = null - val assertions = AssertionsStub( - hasBeenCalledWithVoidAtIndex = { givenProxy, givenIdx -> - capturedProxy = givenProxy - capturedIdx = givenIdx + expectedProxy.getArgumentsForCall = { arrayOf(fixture.fixture()) } + + val container = VerificationChain(references) + + // Then + val error = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenCalledWithVoid() } + } + + error.message mustBe "Expected ${expectedProxy.id} to be void, but the invocation contains Arguments." + } + + @Test + @JsName("fn11") + fun `Given hasBeenCalledWithVoid is called in a Chain it accepts if the given proxy is void`() { + // Given + val expectedProxy = fixture.funProxyFixture() + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), ) - val container = VerificationChain(references, assertions) + expectedProxy.getArgumentsForCall = { arrayOf() } + + val container = VerificationChain(references) // Then invoke(container) { // When expectedProxy.hasBeenCalledWithVoid() } - - capturedProxy sameAs expectedProxy - capturedIdx mustBe callIdx } @Test - @JsName("fn11") + @JsName("fn12") fun `Given hasBeenCalledWith is called in a Chain it fails if the current References are exhausted`() { // Given val id: String = fixture.fixture() @@ -268,7 +265,7 @@ class VerificationChainSpec { } @Test - @JsName("fn12") + @JsName("fn13") fun `Given hasBeenCalledWith is called in a Chain it fails if the expected Proxies was not found`() { // Given val id1: String = fixture.fixture() @@ -293,8 +290,8 @@ class VerificationChainSpec { } @Test - @JsName("fn13") - fun `Given hasBeenCalledWith is called in a Chain it delegates the call to the assertions`() { + @JsName("fn15") + fun `Given hasBeenCalledWith is called in a Chain it fails if the arguments are not matching`() { // Given val id: String = fixture.fixture() val expectedProxy = fixture.funProxyFixture(id = id) @@ -304,34 +301,49 @@ class VerificationChainSpec { ) val expectedValue: String = fixture.fixture() + val actualValue: String = fixture.fixture() - var capturedProxy: Proxy<*, *>? = null - var capturedIdx: Int? = null - var capturedArguments: Array? = null + expectedProxy.getArgumentsForCall = { arrayOf(actualValue) } + + val container = VerificationChain(references) - val assertions = AssertionsStub( - hasBeenCalledWithAtIndex = { givenProxy, givenIdx, givenArguments -> - capturedProxy = givenProxy - capturedIdx = givenIdx - capturedArguments = givenArguments + // Then + val error = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenCalledWith(expectedValue) } + } + + error.message mustBe "Expected ${expectedProxy.id} to be invoked with [$expectedValue], but no matching call was found." + } + + @Test + @JsName("fn16") + fun `Given hasBeenCalledWith is called in a Chain it accepts`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), ) - val container = VerificationChain(references, assertions) + val expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { arrayOf(fixture.fixture(), expectedValue) } + + val container = VerificationChain(references) // Then invoke(container) { // When expectedProxy.hasBeenCalledWith(expectedValue) } - - capturedProxy sameAs expectedProxy - capturedIdx mustBe callIdx - capturedArguments.contentDeepEquals(arrayOf(expectedValue)) mustBe true } @Test - @JsName("fn14") + @JsName("fn17") fun `Given hasBeenStrictlyCalledWith is called in a Chain it fails if the current References are exhausted`() { // Given val id: String = fixture.fixture() @@ -350,7 +362,7 @@ class VerificationChainSpec { } @Test - @JsName("fn15") + @JsName("fn18") fun `Given hasBeenStrictlyCalledWith is called in a Chain it fails if the expected Proxies was not found`() { // Given val id1: String = fixture.fixture() @@ -375,8 +387,37 @@ class VerificationChainSpec { } @Test - @JsName("fn16") - fun `Given hasBeenStrictlyCalledWith is called in a Chain it delegates the call to the assertions`() { + @JsName("fn19") + fun `Given hasBeenStrictlyCalledWith is called in a Chain it fails the arguments do not match`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + val expectedValue: String = fixture.fixture() + val actualValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { arrayOf(actualValue) } + + val container = VerificationChain(references) + + // Then + val error = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenStrictlyCalledWith(expectedValue) + } + } + + error.message mustBe "Expected ${expectedProxy.id} to be invoked with [$expectedValue], but no matching call was found." + } + + @Test + @JsName("fn20") + fun `Given hasBeenStrictlyCalledWith is called in a Chain it fails the arguments are not in linear order`() { // Given val id: String = fixture.fixture() val expectedProxy = fixture.funProxyFixture(id = id) @@ -386,34 +427,49 @@ class VerificationChainSpec { ) val expectedValue: String = fixture.fixture() + val actualValue: String = fixture.fixture() - var capturedProxy: Proxy<*, *>? = null - var capturedIdx: Int? = null - var capturedArguments: Array? = null + expectedProxy.getArgumentsForCall = { arrayOf(actualValue, expectedValue) } + + val container = VerificationChain(references) - val assertions = AssertionsStub( - hasBeenStrictlyCalledWithAtIndex = { givenProxy, givenIdx, givenArguments -> - capturedProxy = givenProxy - capturedIdx = givenIdx - capturedArguments = givenArguments + // Then + val error = assertFailsWith { + invoke(container) { + // When + expectedProxy.hasBeenStrictlyCalledWith(expectedValue) } + } + + error.message mustBe "Expected ${expectedProxy.id} to be invoked with [$expectedValue], but no matching call was found." + } + + @Test + @JsName("fn21") + fun `Given hasBeenStrictlyCalledWith is called in a Chain it accepts the arguments match`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), ) - val container = VerificationChain(references, assertions) + val expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { arrayOf(expectedValue) } + + val container = VerificationChain(references) // Then invoke(container) { // When expectedProxy.hasBeenStrictlyCalledWith(expectedValue) } - - capturedProxy sameAs expectedProxy - capturedIdx mustBe callIdx - capturedArguments.contentDeepEquals(arrayOf(expectedValue)) mustBe true } @Test - @JsName("fn17") + @JsName("fn22") fun `Given hasBeenCalledWithout is called in a Chain it fails if the current References are exhausted`() { // Given val id: String = fixture.fixture() @@ -432,7 +488,7 @@ class VerificationChainSpec { } @Test - @JsName("fn18") + @JsName("fn23") fun `Given hasBeenCalledWithout is called in a Chain it fails if the expected Proxies was not found`() { // Given val id1: String = fixture.fixture() @@ -457,8 +513,8 @@ class VerificationChainSpec { } @Test - @JsName("fn19") - fun `Given hasBeenCalledWithout is called in a Chain it delegates the call to the assertions`() { + @JsName("fn24") + fun `Given hasBeenCalledWithout is called in a Chain it fails if the a argument matches`() { // Given val id: String = fixture.fixture() val expectedProxy = fixture.funProxyFixture(id = id) @@ -469,33 +525,47 @@ class VerificationChainSpec { val expectedValue: String = fixture.fixture() - var capturedProxy: Proxy<*, *>? = null - var capturedIdx: Int? = null - var capturedArguments: Array? = null + expectedProxy.getArgumentsForCall = { arrayOf(expectedValue) } + + val container = VerificationChain(references) - val assertions = AssertionsStub( - hasBeenCalledWithoutAtIndex = { givenProxy, givenIdx, givenArguments -> - capturedProxy = givenProxy - capturedIdx = givenIdx - capturedArguments = givenArguments + val error = assertFailsWith { + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalledWithout(expectedValue) } + } + + error.message mustBe "Illegal value <[$expectedValue]> detected." + } + + @Test + @JsName("fn25") + fun `Given hasBeenCalledWithout is called in a Chain it accepts if the a the agruments do not match`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.funProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), ) - val container = VerificationChain(references, assertions) + val expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { arrayOf(fixture.fixture()) } + + val container = VerificationChain(references) // Then invoke(container) { // When expectedProxy.hasBeenCalledWithout(expectedValue) } - - capturedProxy sameAs expectedProxy - capturedIdx mustBe callIdx - capturedArguments.contentDeepEquals(arrayOf(expectedValue)) mustBe true } @Test - @JsName("fn20") + @JsName("fn26") fun `Given wasGotten is called in a Chain it fails if the current References are exhausted`() { // Given val id: String = fixture.fixture() @@ -514,7 +584,7 @@ class VerificationChainSpec { } @Test - @JsName("fn21") + @JsName("fn27") fun `Given wasGotten is called in a Chain it fails if the expected Proxies was not found`() { // Given val id1: String = fixture.fixture() @@ -539,8 +609,8 @@ class VerificationChainSpec { } @Test - @JsName("fn22") - fun `Given wasGotten is called in a Chain it delegates the call to the assertions`() { + @JsName("fn28") + fun `Given wasGotten is called in a Chain it fails if the was not a Getter`() { // Given val id: String = fixture.fixture() val expectedProxy = fixture.propertyProxyFixture(id = id) @@ -549,30 +619,45 @@ class VerificationChainSpec { Reference(expectedProxy, callIdx), ) - var capturedProxy: Proxy<*, *>? = null - var capturedIdx: Int? = null + expectedProxy.getArgumentsForCall = { GetOrSet.Set(null) } + + val container = VerificationChain(references) - val assertions = AssertionsStub( - wasGottenAtIndex = { givenProxy, givenIdx -> - capturedProxy = givenProxy - capturedIdx = givenIdx + // Then + val error = assertFailsWith { + invoke(container) { + // When + expectedProxy.wasGotten() } + } + + error.message mustBe "Expected a getter and got a setter." + } + + @Test + @JsName("fn29") + fun `Given wasGotten is called in a Chain it accepts if it was a Getter`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), ) - val container = VerificationChain(references, assertions) + expectedProxy.getArgumentsForCall = { GetOrSet.Get } + + val container = VerificationChain(references) // Then invoke(container) { // When expectedProxy.wasGotten() } - - capturedProxy sameAs expectedProxy - capturedIdx mustBe callIdx } @Test - @JsName("fn23") + @JsName("fn30") fun `Given wasSet is called in a Chain it fails if the current References are exhausted`() { // Given val id: String = fixture.fixture() @@ -591,7 +676,7 @@ class VerificationChainSpec { } @Test - @JsName("fn24") + @JsName("fn31") fun `Given wasSet is called in a Chain it fails if the expected Proxies was not found`() { // Given val id1: String = fixture.fixture() @@ -616,8 +701,8 @@ class VerificationChainSpec { } @Test - @JsName("fn25") - fun `Given wasSet is called in a Chain it it delegates the call to the assertions`() { + @JsName("fn32") + fun `Given wasSet is called in a Chain it fails if the was not a Setter`() { // Given val id: String = fixture.fixture() val expectedProxy = fixture.propertyProxyFixture(id = id) @@ -626,30 +711,45 @@ class VerificationChainSpec { Reference(expectedProxy, callIdx), ) - var capturedProxy: Proxy<*, *>? = null - var capturedIdx: Int? = null + expectedProxy.getArgumentsForCall = { GetOrSet.Get } + + val container = VerificationChain(references) - val assertions = AssertionsStub( - wasSetAtIndex = { givenProxy, givenIdx -> - capturedProxy = givenProxy - capturedIdx = givenIdx + // Then + val error = assertFailsWith { + invoke(container) { + // When + expectedProxy.wasSet() } + } + + error.message mustBe "Expected a setter and got a getter." + } + + @Test + @JsName("fn33") + fun `Given wasSet is called in a Chain it accepts if it was a Setter`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), ) - val container = VerificationChain(references, assertions) + expectedProxy.getArgumentsForCall = { GetOrSet.Set(null) } + + val container = VerificationChain(references) // Then invoke(container) { // When expectedProxy.wasSet() } - - capturedProxy sameAs expectedProxy - capturedIdx mustBe callIdx } @Test - @JsName("fn26") + @JsName("fn34") fun `Given wasSetTo is called in a Chain it fails if the current References are exhausted`() { // Given val id: String = fixture.fixture() @@ -668,7 +768,7 @@ class VerificationChainSpec { } @Test - @JsName("fn27") + @JsName("fn35") fun `Given wasSetTo is called in a Chain it fails if the expected Proxies was not found`() { // Given val id1: String = fixture.fixture() @@ -693,8 +793,8 @@ class VerificationChainSpec { } @Test - @JsName("fn28") - fun `Given wasSetTo is called in a Chain it delegates the call to the assertions`() { + @JsName("fn36") + fun `Given wasSetTo is called in a Chain it fails if the a Setter with the given value was not found`() { // Given val id: String = fixture.fixture() val expectedProxy = fixture.propertyProxyFixture(id = id) @@ -703,54 +803,55 @@ class VerificationChainSpec { Reference(expectedProxy, callIdx), ) - val expectedValue: Any? = fixture.fixture() + val expectedValue: String = fixture.fixture() - var capturedProxy: Proxy<*, *>? = null - var capturedIdx: Int? = null - var capturedValue: Any? = null + expectedProxy.getArgumentsForCall = { GetOrSet.Get } - val assertions = AssertionsStub( - wasSetToAtIndex = { givenProxy, givenIdx, argument -> - capturedProxy = givenProxy - capturedIdx = givenIdx - capturedValue = argument + val container = VerificationChain(references) + + // Then + val error = assertFailsWith { + invoke(container) { + // When + expectedProxy.wasSetTo(expectedValue) } + } + + error.message mustBe "Expected ${expectedProxy.id} to be invoked with $expectedValue, but no matching call was found." + } + + @Test + @JsName("fn37") + fun `Given wasSetTo is called in a Chain it accepts if it was a Setter with the given value`() { + // Given + val id: String = fixture.fixture() + val expectedProxy = fixture.propertyProxyFixture(id = id) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), ) - val container = VerificationChain(references, assertions) + val expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { GetOrSet.Set(expectedValue) } + + val container = VerificationChain(references) // Then invoke(container) { // When expectedProxy.wasSetTo(expectedValue) } - - capturedProxy sameAs expectedProxy - capturedIdx mustBe callIdx - capturedValue sameAs expectedValue } @Test - @JsName("fn29") + @JsName("fn38") fun `It respects the pratial order of the chain`() { // Given val proxy1 = fixture.funProxyFixture() val proxy2 = fixture.funProxyFixture() val proxy3 = fixture.propertyProxyFixture() - val expectedProxies = listOf( - proxy1, - proxy3, - proxy2, - proxy1, - proxy3, - proxy2, - proxy3, - proxy1, - ) - - val expectedCallIndices: List = fixture.listFixture(size = 8) - val references = listOf( Reference(proxy3, fixture.fixture()), Reference(proxy3, fixture.fixture()), @@ -759,78 +860,44 @@ class VerificationChainSpec { Reference(proxy3, fixture.fixture()), Reference(proxy3, fixture.fixture()), Reference(proxy3, fixture.fixture()), - Reference(proxy1, expectedCallIndices[0]), - Reference(proxy3, expectedCallIndices[1]), - Reference(proxy2, expectedCallIndices[2]), + Reference(proxy1, 0), + Reference(proxy3, fixture.fixture()), Reference(proxy2, fixture.fixture()), Reference(proxy2, fixture.fixture()), - Reference(proxy1, expectedCallIndices[3]), - Reference(proxy1, fixture.fixture()), - Reference(proxy1, fixture.fixture()), - Reference(proxy1, fixture.fixture()), - Reference(proxy3, expectedCallIndices[4]), - Reference(proxy2, expectedCallIndices[5]), - Reference(proxy3, expectedCallIndices[6]), + Reference(proxy2, fixture.fixture()), + Reference(proxy1, 1), + Reference(proxy1, 2), + Reference(proxy1, 3), + Reference(proxy1, 4), + Reference(proxy3, fixture.fixture()), + Reference(proxy2, fixture.fixture()), + Reference(proxy3, fixture.fixture()), Reference(proxy3, fixture.fixture()), Reference(proxy3, fixture.fixture()), Reference(proxy3, fixture.fixture()), - Reference(proxy1, expectedCallIndices[7]), + Reference(proxy1, 5), ) - val capturedProxies: MutableList> = mutableListOf() - val capturedCallIdx: MutableList = mutableListOf() - - val assertions = AssertionsStub( - hasBeenCalledAtIndex = { givenProxy, givenIdx -> - capturedProxies.add(givenProxy) - capturedCallIdx.add(givenIdx) - }, - hasBeenCalledWithVoidAtIndex = { givenProxy, givenIdx -> - capturedProxies.add(givenProxy) - capturedCallIdx.add(givenIdx) - }, - hasBeenCalledWithAtIndex = { givenProxy, givenIdx, _ -> - capturedProxies.add(givenProxy) - capturedCallIdx.add(givenIdx) - }, - hasBeenStrictlyCalledWithAtIndex = { givenProxy, givenIdx, _ -> - capturedProxies.add(givenProxy) - capturedCallIdx.add(givenIdx) - }, - hasBeenCalledWithoutAtIndex = { givenProxy, givenIdx, _ -> - capturedProxies.add(givenProxy) - capturedCallIdx.add(givenIdx) - }, - wasGottenAtIndex = { givenProxy, givenIdx -> - capturedProxies.add(givenProxy) - capturedCallIdx.add(givenIdx) - }, - wasSetAtIndex = { givenProxy, givenIdx -> - capturedProxies.add(givenProxy) - capturedCallIdx.add(givenIdx) - }, - wasSetToAtIndex = { givenProxy, givenIdx, _ -> - capturedProxies.add(givenProxy) - capturedCallIdx.add(givenIdx) + val expectedValue1: Any = fixture.fixture() + val expectedValue2: Any = fixture.fixture() + + proxy1.getArgumentsForCall = { idx -> + when (idx) { + 2 -> arrayOf(expectedValue1) + 4 -> arrayOf(expectedValue2) + else -> arrayOf(fixture.fixture()) } - ) + } - val chain = VerificationChain(references, assertions) + println(expectedValue1) + println(expectedValue2) + + val chain = VerificationChain(references) // When invoke(chain) { - proxy1.hasBeenCalled() - proxy3.wasGotten() - proxy2.hasBeenCalledWithVoid() - proxy1.hasBeenCalledWith(fixture.fixture()) - proxy3.wasSet() - proxy2.hasBeenStrictlyCalledWith(fixture.fixture()) - proxy3.wasSetTo(fixture.fixture()) - proxy1.hasBeenCalledWithout(fixture.fixture()) + proxy1.hasBeenStrictlyCalledWith(expectedValue1) + proxy1.hasBeenCalledWith(expectedValue2) } - - // Then - capturedProxies mustBe expectedProxies - capturedCallIdx mustBe expectedCallIndices } } diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/ExpectationFactorySpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationContextSpec.kt similarity index 85% rename from kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/ExpectationFactorySpec.kt rename to kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationContextSpec.kt index 20dd47ce..0bd97e13 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/ExpectationFactorySpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationContextSpec.kt @@ -7,19 +7,29 @@ package tech.antibytes.kmock.verification import tech.antibytes.kmock.KMockContract -import tech.antibytes.kmock.KMockContract.Expectation import tech.antibytes.kmock.fixture.funProxyFixture import tech.antibytes.mock.PropertyProxyStub import tech.antibytes.util.test.fixture.fixture import tech.antibytes.util.test.fixture.kotlinFixture import tech.antibytes.util.test.fixture.listFixture +import tech.antibytes.util.test.fulfils import tech.antibytes.util.test.mustBe import kotlin.js.JsName import kotlin.test.Test -class ExpectationFactorySpec { +class VerificationContextSpec { private val fixture = kotlinFixture() + @Test + @JsName("fn0a") + fun `It fulfils VerificationContext`() { + VerificationContext fulfils KMockContract.VerificationContext::class + } + + private fun invoke( + action: VerificationContext.() -> Expectation + ): Expectation = action(VerificationContext) + @Test @JsName("fn0") fun `Given hasBeenCalled is called with a FunProxy it returns a VerficationHandle which contains no matches if there were no calls`() { @@ -28,7 +38,9 @@ class ExpectationFactorySpec { val proxy = fixture.funProxyFixture(name, 0) // When - val actual = proxy.hasBeenCalled() + val actual = invoke { + proxy.hasBeenCalled() + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -49,7 +61,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenCalled() + val actual = invoke { + proxy.hasBeenCalled() + } // Then actual mustBe Expectation(proxy, listOf(0)) @@ -64,7 +78,9 @@ class ExpectationFactorySpec { val proxy = fixture.funProxyFixture(name, 0) // When - val actual = proxy.hasBeenCalledWithVoid() + val actual = invoke { + proxy.hasBeenCalledWithVoid() + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -85,7 +101,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenCalledWithVoid() + val actual = invoke { + proxy.hasBeenCalledWithVoid() + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -107,7 +125,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenCalled() + val actual = invoke { + proxy.hasBeenCalled() + } // Then actual mustBe Expectation(proxy, listOf(0)) @@ -122,7 +142,9 @@ class ExpectationFactorySpec { val proxy = fixture.funProxyFixture(name, 0) // When - val actual = proxy.hasBeenCalledWith() + val actual = invoke { + proxy.hasBeenCalledWith() + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -143,7 +165,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenCalledWith(fixture.fixture()) + val actual = invoke { + proxy.hasBeenCalledWith(fixture.fixture()) + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -166,7 +190,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenCalledWith(*values) + val actual = invoke { + proxy.hasBeenCalledWith(*values) + } // Then actual mustBe Expectation(proxy, listOf(0)) @@ -181,7 +207,9 @@ class ExpectationFactorySpec { val proxy = fixture.funProxyFixture(name, 0) // When - val actual = proxy.hasBeenStrictlyCalledWith() + val actual = invoke { + proxy.hasBeenStrictlyCalledWith() + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -202,7 +230,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenStrictlyCalledWith(fixture.fixture()) + val actual = invoke { + proxy.hasBeenStrictlyCalledWith(fixture.fixture()) + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -225,7 +255,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenStrictlyCalledWith(*values) + val actual = invoke { + proxy.hasBeenStrictlyCalledWith(*values) + } // Then actual mustBe Expectation(proxy, listOf(0)) @@ -240,7 +272,9 @@ class ExpectationFactorySpec { val proxy = fixture.funProxyFixture(name, 0) // When - val actual = proxy.hasBeenCalledWithout() + val actual = invoke { + proxy.hasBeenCalledWithout() + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -262,7 +296,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenCalledWithout(*values) + val actual = invoke { + proxy.hasBeenCalledWithout(*values) + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -284,7 +320,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.hasBeenCalledWithout(fixture.fixture()) + val actual = invoke { + proxy.hasBeenCalledWithout(fixture.fixture()) + } // Then actual mustBe Expectation(proxy, listOf(0)) @@ -306,7 +344,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.wasGotten() + val actual = invoke { + proxy.wasGotten() + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -328,7 +368,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.wasGotten() + val actual = invoke { + proxy.wasGotten() + } // Then actual mustBe Expectation(proxy, listOf(0)) @@ -350,7 +392,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.wasSet() + val actual = invoke { + proxy.wasSet() + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -372,7 +416,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.wasSet() + val actual = invoke { + proxy.wasSet() + } // Then actual mustBe Expectation(proxy, listOf(0)) @@ -394,7 +440,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.wasSetTo(fixture.fixture()) + val actual = invoke { + proxy.wasSetTo(fixture.fixture()) + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -416,7 +464,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.wasSetTo(fixture.fixture()) + val actual = invoke { + proxy.wasSetTo(fixture.fixture()) + } // Then actual mustBe Expectation(proxy, emptyList()) @@ -439,7 +489,9 @@ class ExpectationFactorySpec { } // When - val actual = proxy.wasSetTo(value) + val actual = invoke { + proxy.wasSetTo(value) + } // Then actual mustBe Expectation(proxy, listOf(0)) diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationSpec.kt index 5b83881b..02c337c2 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationSpec.kt @@ -163,31 +163,12 @@ class VerificationSpec { // When verifier.verifyStrictOrder { // Then - this fulfils AssertionChain::class + this fulfils StrictVerificationChain::class } } @Test @JsName("fn9") - fun `Given verifyStrictOrder is called it ensures all references had been evaluated`() { - // Given - val id: String = fixture.fixture() - val verifier = AsserterStub( - listOf( - Reference(fixture.funProxyFixture(id = id), fixture.fixture()) - ) - ) - - val actual = assertFailsWith { - // When - verifier.verifyStrictOrder {} - } - - actual.message mustBe "The given verification chain covers 1 items, but only 0 were expected ($id were referenced)." - } - - @Test - @JsName("fn10") fun `Given verifyOrder is called it uses a StrictVerificationChain`() { // Given val verifier = AsserterStub(emptyList())