From 4c55005946ad9c191371e7a819cbd08a1d866d34 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Tue, 26 Apr 2022 08:05:30 +0200 Subject: [PATCH 1/5] Scope remaining expectation methods --- CHANGELOG.adoc | 3 +- .../SampleControllerAutoSpyFactorySpec.kt | 4 - .../SampleControllerAutoStubFactorySpec.kt | 4 - .../SampleControllerAutoStubRelaxedSpec.kt | 5 - .../example/SampleControllerAutoStubSpec.kt | 4 - .../example/contract/PlatformDecoderSpec.kt | 2 +- .../SampleControllerAutoStubFactoryJsSpec.kt | 4 - .../tech/antibytes/kmock/KMockContract.kt | 70 +++++++++++-- .../kmock/verification/ExpectationFactory.kt | 99 ------------------- .../kmock/verification/ProxyAssertion.kt | 27 +++-- .../kmock/verification/Verification.kt | 30 +++--- .../kmock/verification/VerificationContext.kt | 53 ++++++++++ ...torySpec.kt => VerificationContextSpec.kt} | 98 +++++++++++++----- 13 files changed, 223 insertions(+), 180 deletions(-) delete mode 100644 kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/ExpectationFactory.kt create mode 100644 kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationContext.kt rename kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/{ExpectationFactorySpec.kt => VerificationContextSpec.kt} (85%) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 9f549a94..1b6a8afa 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 which should be used to validate a total linear order of Proxies === Changed @@ -48,6 +48,7 @@ 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 === Deprecated 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/iosTest/kotlin/tech/antibytes/kmock/example/contract/PlatformDecoderSpec.kt b/examples/src/iosTest/kotlin/tech/antibytes/kmock/example/contract/PlatformDecoderSpec.kt index 7afc363b..1d601f2b 100644 --- a/examples/src/iosTest/kotlin/tech/antibytes/kmock/example/contract/PlatformDecoderSpec.kt +++ b/examples/src/iosTest/kotlin/tech/antibytes/kmock/example/contract/PlatformDecoderSpec.kt @@ -13,6 +13,6 @@ import kotlin.test.Test class PlatformDecoderSpec { @Test fun doSomething() { - // TODO + TODO() } } 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..5298210d 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,6 +1292,66 @@ 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 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/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/Verification.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt index 7ded9b9d..506af271 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,16 +83,16 @@ 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. + * @param action chain of Expectation Methods. * @throws AssertionError if given criteria are not met. * @see assertOrder * @author Matthias Geisler @@ -109,14 +101,14 @@ fun Asserter.assertOrder(scope: ChainedAssertion.() -> Any) = runAssertion(Asser "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) = assertOrder(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/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/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)) From e2aefef22d39237fe7a942f7684729ef4005a8c1 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Tue, 26 Apr 2022 08:08:23 +0200 Subject: [PATCH 2/5] VerificationChain -> StrictVerificationChain --- ...ionChain.kt => StrictVerificationChain.kt} | 2 +- .../kmock/verification/Verification.kt | 2 +- ...Spec.kt => StrictVerificationChainSpec.kt} | 62 +++++++++---------- .../kmock/verification/VerificationSpec.kt | 2 +- 4 files changed, 34 insertions(+), 34 deletions(-) rename kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/{VerificationChain.kt => StrictVerificationChain.kt} (99%) rename kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/{VerificationChainSpec.kt => StrictVerificationChainSpec.kt} (92%) diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/StrictVerificationChain.kt similarity index 99% rename from kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt rename to kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/StrictVerificationChain.kt index 74d7dade..d7b6ad88 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/StrictVerificationChain.kt @@ -19,7 +19,7 @@ import tech.antibytes.kmock.KMockContract.Proxy import tech.antibytes.kmock.KMockContract.Reference import tech.antibytes.kmock.util.format -internal class VerificationChain( +internal class StrictVerificationChain( private val references: List, private val assertions: Assertions = Assertions, ) : AssertionChain, ChainedAssertion { 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 506af271..0d3c6b0f 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt @@ -111,4 +111,4 @@ fun Asserter.verifyStrictOrder(action: ChainedAssertion.() -> Any) = assertOrder * @see AssertionContext * @author Matthias Geisler */ -fun Asserter.verifyOrder(action: ChainedAssertion.() -> Any) = runAssertion(VerificationChain(references), action) +fun Asserter.verifyOrder(action: ChainedAssertion.() -> Any) = runAssertion(StrictVerificationChain(references), action) diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt similarity index 92% rename from kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt rename to kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt index 451e9c02..23132af8 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt @@ -23,13 +23,13 @@ import kotlin.js.JsName import kotlin.test.Test import kotlin.test.assertFailsWith -class VerificationChainSpec { +class StrictVerificationChainSpec { private val fixture = kotlinFixture() @Test @JsName("fn0") fun `It fulfils VerificationChain`() { - VerificationChain(emptyList()) fulfils KMockContract.AssertionChain::class + StrictVerificationChain(emptyList()) fulfils KMockContract.AssertionChain::class } @Test @@ -55,7 +55,7 @@ class VerificationChainSpec { Reference(handle2.proxy, 1), ) - val chain = VerificationChain(references) + val chain = StrictVerificationChain(references) // When chain.ensureAllReferencesAreEvaluated() @@ -68,7 +68,7 @@ class VerificationChainSpec { val proxy = fixture.funProxyFixture() // When - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) val error = assertFailsWith { container.ensureVerificationOf(proxy) @@ -94,8 +94,8 @@ class VerificationChainSpec { } private fun invoke( - chain: VerificationChain, - action: VerificationChain.() -> Unit + chain: StrictVerificationChain, + action: StrictVerificationChain.() -> Unit ) = action(chain) @Test @@ -104,7 +104,7 @@ class VerificationChainSpec { // Given val id: String = fixture.fixture() val proxy = fixture.funProxyFixture(id = id) - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) // Then val actual = assertFailsWith { @@ -129,7 +129,7 @@ class VerificationChainSpec { Reference(actualProxy, 0), ) - val container = VerificationChain(references) + val container = StrictVerificationChain(references) // Then val actual = assertFailsWith { @@ -161,7 +161,7 @@ class VerificationChainSpec { } ) - val container = VerificationChain(references, assertions) + val container = StrictVerificationChain(references, assertions) // Then invoke(container) { @@ -179,7 +179,7 @@ class VerificationChainSpec { // Given val id: String = fixture.fixture() val proxy = fixture.funProxyFixture(id = id) - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) // Then val actual = assertFailsWith { @@ -204,7 +204,7 @@ class VerificationChainSpec { Reference(actualProxy, 0), ) - val container = VerificationChain(references) + val container = StrictVerificationChain(references) // Then val actual = assertFailsWith { @@ -236,7 +236,7 @@ class VerificationChainSpec { } ) - val container = VerificationChain(references, assertions) + val container = StrictVerificationChain(references, assertions) // Then invoke(container) { @@ -254,7 +254,7 @@ class VerificationChainSpec { // Given val id: String = fixture.fixture() val proxy = fixture.funProxyFixture(id = id) - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) // Then val actual = assertFailsWith { @@ -279,7 +279,7 @@ class VerificationChainSpec { Reference(actualProxy, 0), ) - val container = VerificationChain(references) + val container = StrictVerificationChain(references) // Then val actual = assertFailsWith { @@ -317,7 +317,7 @@ class VerificationChainSpec { } ) - val container = VerificationChain(references, assertions) + val container = StrictVerificationChain(references, assertions) // Then invoke(container) { @@ -336,7 +336,7 @@ class VerificationChainSpec { // Given val id: String = fixture.fixture() val proxy = fixture.funProxyFixture(id = id) - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) // Then val actual = assertFailsWith { @@ -361,7 +361,7 @@ class VerificationChainSpec { Reference(actualProxy, 0), ) - val container = VerificationChain(references) + val container = StrictVerificationChain(references) // Then val actual = assertFailsWith { @@ -399,7 +399,7 @@ class VerificationChainSpec { } ) - val container = VerificationChain(references, assertions) + val container = StrictVerificationChain(references, assertions) // Then invoke(container) { @@ -418,7 +418,7 @@ class VerificationChainSpec { // Given val id: String = fixture.fixture() val proxy = fixture.funProxyFixture(id = id) - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) // Then val actual = assertFailsWith { @@ -443,7 +443,7 @@ class VerificationChainSpec { Reference(actualProxy, 0), ) - val container = VerificationChain(references) + val container = StrictVerificationChain(references) // Then val actual = assertFailsWith { @@ -481,7 +481,7 @@ class VerificationChainSpec { } ) - val container = VerificationChain(references, assertions) + val container = StrictVerificationChain(references, assertions) // Then invoke(container) { @@ -500,7 +500,7 @@ class VerificationChainSpec { // Given val id: String = fixture.fixture() val proxy = fixture.propertyProxyFixture(id = id) - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) // Then val actual = assertFailsWith { @@ -525,7 +525,7 @@ class VerificationChainSpec { Reference(actualProxy, 0), ) - val container = VerificationChain(references) + val container = StrictVerificationChain(references) // Then val actual = assertFailsWith { @@ -559,7 +559,7 @@ class VerificationChainSpec { } ) - val container = VerificationChain(references, assertions) + val container = StrictVerificationChain(references, assertions) // Then invoke(container) { @@ -577,7 +577,7 @@ class VerificationChainSpec { // Given val id: String = fixture.fixture() val proxy = fixture.propertyProxyFixture(id = id) - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) // Then val actual = assertFailsWith { @@ -602,7 +602,7 @@ class VerificationChainSpec { Reference(actualProxy, 0), ) - val container = VerificationChain(references) + val container = StrictVerificationChain(references) // Then val actual = assertFailsWith { @@ -636,7 +636,7 @@ class VerificationChainSpec { } ) - val container = VerificationChain(references, assertions) + val container = StrictVerificationChain(references, assertions) // Then invoke(container) { @@ -654,7 +654,7 @@ class VerificationChainSpec { // Given val id: String = fixture.fixture() val proxy = fixture.propertyProxyFixture(id = id) - val container = VerificationChain(emptyList()) + val container = StrictVerificationChain(emptyList()) // Then val actual = assertFailsWith { @@ -679,7 +679,7 @@ class VerificationChainSpec { Reference(actualProxy, 0), ) - val container = VerificationChain(references) + val container = StrictVerificationChain(references) // Then val actual = assertFailsWith { @@ -717,7 +717,7 @@ class VerificationChainSpec { } ) - val container = VerificationChain(references, assertions) + val container = StrictVerificationChain(references, assertions) // Then invoke(container) { @@ -815,7 +815,7 @@ class VerificationChainSpec { } ) - val chain = VerificationChain(references, assertions) + val chain = StrictVerificationChain(references, assertions) // When invoke(chain) { 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..f4273552 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationSpec.kt @@ -195,7 +195,7 @@ class VerificationSpec { // When verifier.verifyOrder { // Then - this fulfils VerificationChain::class + this fulfils StrictVerificationChain::class } } } From e9ed164b74d74b85c4f79283a83a23ff066e9ad4 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Tue, 26 Apr 2022 09:58:08 +0200 Subject: [PATCH 3/5] Add VerificationChain --- .../tech/antibytes/kmock/KMockContract.kt | 5 +- .../kmock/verification/VerificationChain.kt | 201 ++++ .../kmock/verification/AssertionChainSpec.kt | 2 +- .../kmock/verification/AssertionsSpec.kt | 4 +- .../StrictVerificationChainSpec.kt | 26 +- .../verification/VerificationChainSpec.kt | 905 ++++++++++++++++++ 6 files changed, 1123 insertions(+), 20 deletions(-) create mode 100644 kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt create mode 100644 kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt index 5298210d..cfd347d3 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt @@ -1358,7 +1358,7 @@ object KMockContract { */ 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<*, *>) @@ -1407,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." @@ -1419,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/VerificationChain.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt new file mode 100644 index 00000000..d09ef977 --- /dev/null +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt @@ -0,0 +1,201 @@ +/* + * 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 +import tech.antibytes.kmock.KMockContract.FunProxy +import tech.antibytes.kmock.KMockContract.PropertyProxy +import tech.antibytes.kmock.KMockContract.Proxy +import tech.antibytes.kmock.KMockContract.AssertionChain +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.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.VOID_FUNCTION +import tech.antibytes.kmock.util.format + +internal class VerificationChain( + private val references: List, +) : 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 + + invocation.update { actual + 1 } + + action(expectedCallIdxReference) + } + + 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 -> + 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 -> + 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 -> + 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 -> + 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 -> + 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 -> + 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 -> + 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 + ) + ) + } + } + } + } + + @Throws(AssertionError::class) + override fun ensureAllReferencesAreEvaluated() = Unit +} 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 index 23132af8..bbd4bded 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt @@ -28,31 +28,27 @@ class StrictVerificationChainSpec { @Test @JsName("fn0") - fun `It fulfils VerificationChain`() { + fun `It fulfils AssertionChain`() { StrictVerificationChain(emptyList()) fulfils KMockContract.AssertionChain::class } @Test @JsName("fn1") fun `It fulfils ChainedAssertion`() { - AssertionChain(emptyList()) fulfils KMockContract.ChainedAssertion::class + StrictVerificationChain(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 = StrictVerificationChain(references) @@ -75,7 +71,7 @@ class StrictVerificationChainSpec { } // 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 +79,7 @@ class StrictVerificationChainSpec { fun `Given ensureVerification it accepts if the given Proxy is part of it`() { // Given val proxy = fixture.funProxyFixture() - val container = AssertionChain( + val container = StrictVerificationChain( listOf( Reference(proxy, 0) ) diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt new file mode 100644 index 00000000..f219a1ac --- /dev/null +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt @@ -0,0 +1,905 @@ +/* + * 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.GetOrSet +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 kotlin.js.JsName +import kotlin.test.Test +import kotlin.test.assertFailsWith + +class VerificationChainSpec { + private val fixture = kotlinFixture() + + @Test + @JsName("fn0") + fun `It fulfils AssertionChain`() { + VerificationChain(emptyList()) fulfils KMockContract.AssertionChain::class + } + + @Test + @JsName("fn1") + fun `It fulfils ChainedAssertion`() { + VerificationChain(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 = VerificationChain(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 = VerificationChain(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 = VerificationChain( + listOf( + Reference(proxy, 0) + ) + ) + + // When + container.ensureVerificationOf(proxy) + } + + private fun invoke( + chain: VerificationChain, + action: VerificationChain.() -> 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 = VerificationChain(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 = VerificationChain(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), + ) + + expectedProxy.getArgumentsForCall = { arrayOf() } + + val container = VerificationChain(references) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalled() + } + } + + @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 = VerificationChain(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 = VerificationChain(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 fails if the given proxy is not void`() { + // Given + val expectedProxy = fixture.funProxyFixture() + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + 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), + ) + + expectedProxy.getArgumentsForCall = { arrayOf() } + + val container = VerificationChain(references) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalledWithVoid() + } + } + + @Test + @JsName("fn12") + 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 = VerificationChain(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("fn13") + 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 = VerificationChain(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("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) + 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.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 expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { arrayOf(fixture.fixture(), expectedValue) } + + val container = VerificationChain(references) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalledWith(expectedValue) + } + } + + @Test + @JsName("fn17") + 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 = VerificationChain(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("fn18") + 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 = VerificationChain(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("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) + 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, expectedValue) } + + 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("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 expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { arrayOf(expectedValue) } + + val container = VerificationChain(references) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenStrictlyCalledWith(expectedValue) + } + } + + @Test + @JsName("fn22") + 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 = VerificationChain(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("fn23") + 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 = VerificationChain(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("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) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + val expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { arrayOf(expectedValue) } + + val container = VerificationChain(references) + + 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 expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { arrayOf(fixture.fixture()) } + + val container = VerificationChain(references) + + // Then + invoke(container) { + // When + expectedProxy.hasBeenCalledWithout(expectedValue) + } + } + + @Test + @JsName("fn26") + 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 = VerificationChain(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("fn27") + 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 = VerificationChain(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("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) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + expectedProxy.getArgumentsForCall = { GetOrSet.Set(null) } + + val container = VerificationChain(references) + + // 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), + ) + + expectedProxy.getArgumentsForCall = { GetOrSet.Get } + + val container = VerificationChain(references) + + // Then + invoke(container) { + // When + expectedProxy.wasGotten() + } + } + + @Test + @JsName("fn30") + 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 = VerificationChain(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("fn31") + 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 = VerificationChain(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("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) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + expectedProxy.getArgumentsForCall = { GetOrSet.Get } + + val container = VerificationChain(references) + + // 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), + ) + + expectedProxy.getArgumentsForCall = { GetOrSet.Set(null) } + + val container = VerificationChain(references) + + // Then + invoke(container) { + // When + expectedProxy.wasSet() + } + } + + @Test + @JsName("fn34") + 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 = VerificationChain(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("fn35") + 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 = VerificationChain(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("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) + val callIdx: Int = fixture.fixture() + val references = listOf( + Reference(expectedProxy, callIdx), + ) + + val expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { GetOrSet.Get } + + 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 expectedValue: String = fixture.fixture() + + expectedProxy.getArgumentsForCall = { GetOrSet.Set(expectedValue) } + + val container = VerificationChain(references) + + // Then + invoke(container) { + // When + expectedProxy.wasSetTo(expectedValue) + } + } + + @Test + @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 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, 0), + Reference(proxy3, fixture.fixture()), + Reference(proxy2, fixture.fixture()), + Reference(proxy2, fixture.fixture()), + 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, 5), + ) + + 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()) + } + } + + println(expectedValue1) + println(expectedValue2) + + val chain = VerificationChain(references) + + // When + invoke(chain) { + proxy1.hasBeenStrictlyCalledWith(expectedValue1) + proxy1.hasBeenCalledWith(expectedValue2) + } + } +} From 2555533a65aa3a19c91807e1814de2198b6481f4 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Tue, 26 Apr 2022 10:01:22 +0200 Subject: [PATCH 4/5] Wire VerificationChain in --- .../kmock/verification/Verification.kt | 10 ++++---- .../kmock/verification/VerificationSpec.kt | 23 ++----------------- 2 files changed, 6 insertions(+), 27 deletions(-) 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 0d3c6b0f..ca387050 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt @@ -97,11 +97,9 @@ fun Asserter.assertOrder(action: ChainedAssertion.() -> Any) = runAssertion(Asse * @see assertOrder * @author Matthias Geisler */ -@Deprecated( - "This will be removed with version 1.0. Use assertOrder instead.", - ReplaceWith("assertOrder(scope)") -) -fun Asserter.verifyStrictOrder(action: ChainedAssertion.() -> Any) = assertOrder(action) +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. @@ -111,4 +109,4 @@ fun Asserter.verifyStrictOrder(action: ChainedAssertion.() -> Any) = assertOrder * @see AssertionContext * @author Matthias Geisler */ -fun Asserter.verifyOrder(action: ChainedAssertion.() -> Any) = runAssertion(StrictVerificationChain(references), action) +fun Asserter.verifyOrder(action: ChainedAssertion.() -> Any) = runAssertion(VerificationChain(references), action) 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 f4273552..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()) @@ -195,7 +176,7 @@ class VerificationSpec { // When verifier.verifyOrder { // Then - this fulfils StrictVerificationChain::class + this fulfils VerificationChain::class } } } From dce7446ac368a5c357452f26e877320d71d98e51 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Tue, 26 Apr 2022 10:12:27 +0200 Subject: [PATCH 5/5] Update doc on new VerificationChain --- CHANGELOG.adoc | 4 ++-- .../antibytes/kmock/example/contract/PlatformDecoderSpec.kt | 2 +- .../kotlin/tech/antibytes/kmock/verification/Asserter.kt | 3 ++- .../antibytes/kmock/verification/NonFreezingAsserter.kt | 3 ++- .../tech/antibytes/kmock/verification/Verification.kt | 4 ++-- .../tech/antibytes/kmock/verification/VerificationChain.kt | 6 +++--- .../kmock/verification/StrictVerificationChainSpec.kt | 1 - .../antibytes/kmock/verification/VerificationChainSpec.kt | 2 -- 8 files changed, 12 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 1b6a8afa..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 and which should be used to validate a total linear order of Proxies +* `assertOrder` in order to make the names more consistent and preserves the old behaviour of `verifyStrictOrder` === Changed @@ -49,11 +49,11 @@ toc::[] * 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/iosTest/kotlin/tech/antibytes/kmock/example/contract/PlatformDecoderSpec.kt b/examples/src/iosTest/kotlin/tech/antibytes/kmock/example/contract/PlatformDecoderSpec.kt index 1d601f2b..7afc363b 100644 --- a/examples/src/iosTest/kotlin/tech/antibytes/kmock/example/contract/PlatformDecoderSpec.kt +++ b/examples/src/iosTest/kotlin/tech/antibytes/kmock/example/contract/PlatformDecoderSpec.kt @@ -13,6 +13,6 @@ import kotlin.test.Test class PlatformDecoderSpec { @Test fun doSomething() { - TODO() + // TODO } } 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/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/Verification.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt index ca387050..345da5cf 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/Verification.kt @@ -91,10 +91,10 @@ fun runAssertion( fun Asserter.assertOrder(action: ChainedAssertion.() -> Any) = runAssertion(AssertionChain(references), action) /** - * Alias of assertOrder. + * 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 */ fun Asserter.verifyStrictOrder(action: ChainedAssertion.() -> Any) { 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 d09ef977..3a0c798d 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/verification/VerificationChain.kt @@ -9,18 +9,18 @@ package tech.antibytes.kmock.verification import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update import tech.antibytes.kmock.KMockContract -import tech.antibytes.kmock.KMockContract.FunProxy -import tech.antibytes.kmock.KMockContract.PropertyProxy -import tech.antibytes.kmock.KMockContract.Proxy import tech.antibytes.kmock.KMockContract.AssertionChain 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.VOID_FUNCTION import tech.antibytes.kmock.util.format diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt index bbd4bded..b34fe752 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/StrictVerificationChainSpec.kt @@ -9,7 +9,6 @@ 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.fixtureVerificationHandle import tech.antibytes.kmock.fixture.funProxyFixture import tech.antibytes.kmock.fixture.propertyProxyFixture import tech.antibytes.mock.AssertionsStub 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 f219a1ac..8e609c9a 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/verification/VerificationChainSpec.kt @@ -11,10 +11,8 @@ import tech.antibytes.kmock.KMockContract.GetOrSet 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 kotlin.js.JsName