From 0bd1174bbdeaa2b6026c6399ab0f824349a5f118 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Fri, 11 Feb 2022 17:28:14 +0100 Subject: [PATCH 1/2] Add example --- examples/build.gradle.kts | 165 ++++++++++++++++++ examples/src/androidMain/AndroidManifest.xml | 7 + .../kmock/example/ExampleContract.kt | 30 ++++ .../kmock/example/SampleController.kt | 22 +++ .../kmock/example/SampleControllerSpec.kt | 128 ++++++++++++++ .../gradle/kmock/dependency/Version.kt | 2 +- .../kotlin/tech/antibytes/kmock/FunMockery.kt | 4 + .../tech/antibytes/kmock/KMockContract.kt | 3 + .../tech/antibytes/kmock/PropertyMockery.kt | 4 + .../kotlin/tech/antibytes/kmock/Verifier.kt | 4 + .../antibytes/kmock/ArgumentMatcherSpec.kt | 50 ++++-- settings.gradle.kts | 1 + 12 files changed, 407 insertions(+), 13 deletions(-) create mode 100644 examples/build.gradle.kts create mode 100644 examples/src/androidMain/AndroidManifest.xml create mode 100644 examples/src/commonMain/kotlin/tech/antibytes/kmock/example/ExampleContract.kt create mode 100644 examples/src/commonMain/kotlin/tech/antibytes/kmock/example/SampleController.kt create mode 100644 examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerSpec.kt diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts new file mode 100644 index 00000000..63d08341 --- /dev/null +++ b/examples/build.gradle.kts @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2022 Matthias Geisler (bitPogo) / All rights reserved. + * + * Use of this source code is governed by Apache v2.0 + */ + +import tech.antibytes.gradle.dependency.Dependency +import tech.antibytes.gradle.kmock.config.KMockConfiguration +import tech.antibytes.gradle.kmock.dependency.Dependency as LocalDependency + +plugins { + id("org.jetbrains.kotlin.multiplatform") + + // Android + id("com.android.library") + + id("tech.antibytes.gradle.configuration") + id("tech.antibytes.gradle.coverage") +} + +group = KMockConfiguration.group + +kotlin { + android() + + js(IR) { + nodejs() + browser() + } + + jvm() + + ios() + + linuxX64() + + sourceSets { + val commonMain by getting { + dependencies { + implementation(Dependency.multiplatform.kotlin.common) + implementation(Dependency.multiplatform.stately.isolate) + implementation(Dependency.multiplatform.stately.concurrency) + + implementation(LocalDependency.antibytes.test.core) + } + } + val commonTest by getting { + dependencies { + implementation(Dependency.multiplatform.test.common) + implementation(Dependency.multiplatform.test.annotations) + implementation(Dependency.multiplatform.coroutines.common) + + implementation(LocalDependency.antibytes.test.annotations) + implementation(LocalDependency.antibytes.test.coroutine) + implementation(LocalDependency.antibytes.test.fixture) + + api(project(":kmock")) + } + } + + val androidMain by getting { + dependencies { + dependsOn(commonMain) + implementation(Dependency.multiplatform.kotlin.android) + } + } + val androidTest by getting { + dependencies { + dependsOn(commonTest) + + implementation(Dependency.multiplatform.test.jvm) + implementation(Dependency.multiplatform.test.junit) + implementation(Dependency.android.test.robolectric) + } + } + + val jsMain by getting { + dependencies { + dependsOn(commonMain) + implementation(Dependency.multiplatform.kotlin.js) + implementation(Dependency.js.nodejs) + } + } + val jsTest by getting { + dependencies { + dependsOn(commonTest) + + implementation(Dependency.multiplatform.test.js) + } + } + + val jvmMain by getting { + dependencies { + dependsOn(commonMain) + implementation(Dependency.multiplatform.kotlin.jdk8) + } + } + val jvmTest by getting { + dependencies { + dependsOn(commonTest) + + implementation(Dependency.multiplatform.test.jvm) + implementation(Dependency.multiplatform.test.junit) + } + } + + val nativeMain by creating { + dependencies { + dependsOn(commonMain) + } + } + + val nativeTest by creating { + dependencies { + dependsOn(commonTest) + } + } + + val darwinMain by creating { + dependencies { + dependsOn(nativeMain) + } + } + val darwinTest by creating { + dependencies { + dependsOn(nativeTest) + } + } + + val otherMain by creating { + dependencies { + dependsOn(nativeMain) + } + } + + val otherTest by creating { + dependencies { + dependsOn(nativeTest) + } + } + + val linuxX64Main by getting { + dependencies { + dependsOn(otherMain) + } + } + + val linuxX64Test by getting { + dependencies { + dependsOn(otherTest) + } + } + + val iosMain by getting { + dependencies { + dependsOn(darwinMain) + } + } + val iosTest by getting { + dependencies { + dependsOn(darwinTest) + } + } + } +} diff --git a/examples/src/androidMain/AndroidManifest.xml b/examples/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..773cbb96 --- /dev/null +++ b/examples/src/androidMain/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + diff --git a/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/ExampleContract.kt b/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/ExampleContract.kt new file mode 100644 index 00000000..8c4b0dce --- /dev/null +++ b/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/ExampleContract.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 Matthias Geisler (bitPogo) / All rights reserved. + * + * Use of this source code is governed by Apache v2.0 + */ + +package tech.antibytes.kmock.example + +interface ExampleContract { + interface SampleDomainObject { + var id: String + val value: Int + } + + interface SampleRemoteRepository { + suspend fun fetch(url: String): SampleDomainObject + fun find(id: String): SampleDomainObject + } + + interface SampleLocalRepository { + suspend fun store(id: String, value: Int): SampleDomainObject + fun contains(id: String): Boolean + fun fetch(id: String): SampleDomainObject + } + + interface SampleController { + suspend fun fetchAndStore(url: String): SampleDomainObject + fun find(id: String): SampleDomainObject + } +} diff --git a/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/SampleController.kt b/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/SampleController.kt new file mode 100644 index 00000000..64ae2234 --- /dev/null +++ b/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/SampleController.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 Matthias Geisler (bitPogo) / All rights reserved. + * + * Use of this source code is governed by Apache v2.0 + */ + +package tech.antibytes.kmock.example + +class SampleController( + private val localRepository: ExampleContract.SampleLocalRepository, + private val remoteRepository: ExampleContract.SampleRemoteRepository +) : ExampleContract.SampleController { + override suspend fun fetchAndStore(url: String): ExampleContract.SampleDomainObject { + val domainObject = remoteRepository.fetch(url) + + return localRepository.store(domainObject.id, domainObject.value) + } + + override fun find(id: String): ExampleContract.SampleDomainObject { + TODO("Not yet implemented") + } +} diff --git a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerSpec.kt b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerSpec.kt new file mode 100644 index 00000000..9b65caaa --- /dev/null +++ b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerSpec.kt @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022 Matthias Geisler (bitPogo) / All rights reserved. + * + * Use of this source code is governed by Apache v2.0 + */ + +package tech.antibytes.kmock.example + +import tech.antibytes.kmock.AsyncFunMockery +import tech.antibytes.kmock.PropertyMockery +import tech.antibytes.kmock.example.ExampleContract.SampleLocalRepository +import tech.antibytes.kmock.example.ExampleContract.SampleRemoteRepository +import tech.antibytes.kmock.example.ExampleContract.SampleDomainObject +import tech.antibytes.kmock.KMockContract.Collector +import tech.antibytes.kmock.SyncFunMockery +import tech.antibytes.kmock.Verifier +import tech.antibytes.kmock.verify +import tech.antibytes.kmock.verifyOrder +import tech.antibytes.kmock.verifyStrictOrder +import tech.antibytes.kmock.wasGotten +import tech.antibytes.kmock.withArguments +import tech.antibytes.kmock.withSameArguments +import tech.antibytes.util.test.coroutine.AsyncTestReturnValue +import tech.antibytes.util.test.coroutine.runBlockingTest +import tech.antibytes.util.test.fixture.fixture +import tech.antibytes.util.test.fixture.kotlinFixture +import tech.antibytes.util.test.fulfils +import tech.antibytes.util.test.mustBe +import kotlin.js.JsName +import kotlin.test.Test + +class SampleControllerSpec { + private val fixture = kotlinFixture() + + @Test + @JsName("fn0") + fun `It fulfils SampleController`() { + SampleController( + SampleLocalRepositoryStub(), + SampleRemoteRepositoryStub() + ) fulfils ExampleContract.SampleController::class + } + + @Test + @JsName("fn1") + fun `Given fetchAndStore it fetches and stores DomainObjects`(): AsyncTestReturnValue { + // Given + val verifier = Verifier() + + val url = fixture.fixture() + val id = fixture.fixture() + val number = fixture.fixture() + + val local = SampleLocalRepositoryStub(verifier) + val remote = SampleRemoteRepositoryStub(verifier) + val domainObject = SampleDomainObjectStub(verifier) + + domainObject.propId.get = id + domainObject.propValue.get = number + + remote.fetch.returnValue = domainObject + local.store.returnValue = domainObject + + // When + val controller = SampleController(local, remote) + return runBlockingTest { + val actual = controller.fetchAndStore(url) + + // Then + actual mustBe domainObject + + verify(exactly = 1) { remote.fetch.withSameArguments(url) } + verify(exactly = 1) { local.store.withSameArguments(id, number) } + + verifier.verifyStrictOrder { + withArguments(remote.fetch, url) + add(domainObject.propId.wasGotten()) + add(domainObject.propValue.wasGotten()) + withSameArguments(local.store, id, number) + } + + verifier.verifyOrder { + withArguments(remote.fetch, url) + withSameArguments(local.store, id, number) + } + } + } +} + + +private class SampleDomainObjectStub( + verifier: Collector = Collector { _, _ -> Unit } +) : SampleDomainObject { + val propId = PropertyMockery("id", verifier) + val propValue = PropertyMockery("value", verifier) + + override var id: String + get() = propId.onGet() + set(value) = propId.onSet(value) + + override val value: Int + get() = propValue.onGet() +} + +private class SampleRemoteRepositoryStub( + verifier: Collector = Collector { _, _ -> Unit } +) : SampleRemoteRepository { + val fetch = AsyncFunMockery SampleDomainObject>("fetch", verifier) + val find = SyncFunMockery SampleDomainObject>("find", verifier) + + override suspend fun fetch(url: String): SampleDomainObject = fetch.invoke(url) + + override fun find(id: String): SampleDomainObject = find.invoke(id) +} + +private class SampleLocalRepositoryStub( + verifier: Collector = Collector { _, _ -> Unit } +): SampleLocalRepository { + val store = AsyncFunMockery SampleDomainObject>("store", verifier) + val contains = SyncFunMockery Boolean>("contains", verifier) + val fetch = SyncFunMockery SampleDomainObject>("fetch", verifier) + + override suspend fun store(id: String, value: Int): SampleDomainObject = store.invoke(id, value) + + override fun contains(id: String): Boolean = contains.invoke(id) + + override fun fetch(id: String): SampleDomainObject = fetch.invoke(id) +} diff --git a/gradlePlugin/kmock-dependency/src/main/kotlin/tech/antibytes/gradle/kmock/dependency/Version.kt b/gradlePlugin/kmock-dependency/src/main/kotlin/tech/antibytes/gradle/kmock/dependency/Version.kt index b86fad50..f4c7e8ec 100644 --- a/gradlePlugin/kmock-dependency/src/main/kotlin/tech/antibytes/gradle/kmock/dependency/Version.kt +++ b/gradlePlugin/kmock-dependency/src/main/kotlin/tech/antibytes/gradle/kmock/dependency/Version.kt @@ -14,7 +14,7 @@ object Version { /** * [AnitBytes GradlePlugins](https://github.com/bitPogo/gradle-plugins) */ - const val antibytes = "99eff34" + const val antibytes = "165ee05" /** * [Spotless](https://plugins.gradle.org/plugin/com.diffplug.gradle.spotless) diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/FunMockery.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/FunMockery.kt index 2d185606..01c90b42 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/FunMockery.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/FunMockery.kt @@ -130,4 +130,8 @@ abstract class FunMockery>( } override fun getArgumentsForCall(callIndex: Int): Array? = arguments.access { it[callIndex] } + + override fun clear() { + TODO("Not yet implemented") + } } diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt index a9c7fd73..77be5792 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt @@ -12,6 +12,7 @@ interface KMockContract { val calls: Int fun getArgumentsForCall(callIndex: Int): Arguments + fun clear() } interface FunMockery> : Mockery?> { @@ -322,6 +323,8 @@ interface KMockContract { interface Verifier { val references: List + + fun clear() } companion object { diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/PropertyMockery.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/PropertyMockery.kt index 757b4569..f0c9bcf0 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/PropertyMockery.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/PropertyMockery.kt @@ -115,4 +115,8 @@ class PropertyMockery( } override fun getArgumentsForCall(callIndex: Int): GetOrSet = arguments.access { it[callIndex] } + + override fun clear() { + TODO("Not yet implemented") + } } diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verifier.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verifier.kt index d4b42490..f4016c55 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verifier.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verifier.kt @@ -21,4 +21,8 @@ class Verifier : KMockContract.Verifier, KMockContract.Collector { references.add(Reference(referredMock, referredCall)) } } + + override fun clear() { + TODO("Not yet implemented") + } } diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/ArgumentMatcherSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/ArgumentMatcherSpec.kt index f8e3be10..f7e0c64d 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/ArgumentMatcherSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/ArgumentMatcherSpec.kt @@ -200,6 +200,32 @@ class ArgumentMatcherSpec { @Test @JsName("fn14") + fun `Given withoutArgument is called with an Argument it returns false if the Array contains no Argument`() { + // Given + val array = null + + // When + val actual = array.withoutArguments() + + // Then + actual mustBe false + } + + @Test + @JsName("fn15") + fun `Given withoutArgument is called with an Argument it returns true if the Array contains Arguments`() { + // Given + val array = null + + // When + val actual = array.withoutArguments(fixture.listFixture().toTypedArray()) + + // Then + actual mustBe true + } + + @Test + @JsName("fn16") fun `Given withoutArgument is called with an Argument it returns true if the Array contains not the given Argument`() { // Given val array = fixture.listFixture().toTypedArray() @@ -212,7 +238,7 @@ class ArgumentMatcherSpec { } @Test - @JsName("fn15") + @JsName("fn17") fun `Given withoutArgument is called with an Argument it returns false if the Array contains the given Argument`() { // Given val array = fixture.listFixture().toTypedArray() @@ -225,7 +251,7 @@ class ArgumentMatcherSpec { } @Test - @JsName("fn16") + @JsName("fn18") fun `Given withoutArgument is called without Argument it returns true if the Array contains Argument`() { // Given val array = fixture.listFixture().toTypedArray() @@ -238,7 +264,7 @@ class ArgumentMatcherSpec { } @Test - @JsName("fn17") + @JsName("fn19") fun `Given withoutArgument is called with Arguments it returns false if the Array is null`() { // Given val array = fixture.listFixture().toTypedArray() @@ -251,7 +277,7 @@ class ArgumentMatcherSpec { } @Test - @JsName("fn18") + @JsName("fn20") fun `Given withoutArgument is called with Arguments it returns true if the Array none of given Arguments matches`() { // Given val array = fixture.listFixture(size = 8).toTypedArray() @@ -265,7 +291,7 @@ class ArgumentMatcherSpec { } @Test - @JsName("fn19") + @JsName("fn21") fun `Given withoutArgument is called with Arguments it returns false if the Array matches at least one of given Arguments`() { // Given val array = fixture.listFixture(size = 8).toTypedArray() @@ -283,43 +309,43 @@ class ArgumentMatcherSpec { } @Test - @JsName("fn20") + @JsName("fn22") fun `Given wasGotten is called it returns false if it is attacht to Set`() { KMockContract.GetOrSet.Set(null).wasGotten() mustBe false } @Test - @JsName("fn21") + @JsName("fn23") fun `Given wasGotten is called it returns true if it is attacht to Get`() { KMockContract.GetOrSet.Get.wasGotten() mustBe true } @Test - @JsName("fn22") + @JsName("fn24") fun `Given wasSet is called it returns false if it is attacht to Get`() { KMockContract.GetOrSet.Get.wasSet() mustBe false } @Test - @JsName("fn23") + @JsName("fn25") fun `Given wasGotten is called it returns true if it is attacht to Set`() { KMockContract.GetOrSet.Set(null).wasSet() mustBe true } @Test - @JsName("fn24") + @JsName("fn26") fun `Given wasSetTo is called it returns false if it is attacht to Get`() { KMockContract.GetOrSet.Get.wasSetTo(null) mustBe false } @Test - @JsName("fn25") + @JsName("fn27") fun `Given wasSetTo is called it returns false if the values do not match`() { KMockContract.GetOrSet.Set(fixture.fixture()).wasSetTo(fixture.fixture()) mustBe false } @Test - @JsName("fn26") + @JsName("fn28") fun `Given wasSetTo is called it returns true if the values do math`() { val value: Any = fixture.fixture() KMockContract.GetOrSet.Set(value).wasSetTo(value) mustBe true diff --git a/settings.gradle.kts b/settings.gradle.kts index a630934c..14a50294 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -19,6 +19,7 @@ plugins { include( ":kmock", + ":examples", ) buildCache { From 166d02eba1a2439d536a207bea8d38b43453a584 Mon Sep 17 00:00:00 2001 From: Matthias Geisler Date: Fri, 11 Feb 2022 19:57:31 +0100 Subject: [PATCH 2/2] Adjust for heavy coroutines --- examples/build.gradle.kts | 2 +- .../kmock/example/ExampleContract.kt | 4 +- .../kmock/example/SampleController.kt | 28 ++++- .../kmock/example/SampleControllerSpec.kt | 113 ++++++++++++++---- .../tech/antibytes/kmock/KMockContract.kt | 2 +- .../tech/antibytes/kmock/Verification.kt | 10 +- .../tech/antibytes/kmock/VerificationSpec.kt | 6 +- .../tech/antibytes/mock/FunMockeryStub.kt | 4 + .../antibytes/mock/PropertyMockeryStub.kt | 4 + .../tech/antibytes/mock/VerifierStub.kt | 6 +- 10 files changed, 141 insertions(+), 38 deletions(-) diff --git a/examples/build.gradle.kts b/examples/build.gradle.kts index 63d08341..6d91bd66 100644 --- a/examples/build.gradle.kts +++ b/examples/build.gradle.kts @@ -38,6 +38,7 @@ kotlin { val commonMain by getting { dependencies { implementation(Dependency.multiplatform.kotlin.common) + implementation(Dependency.multiplatform.coroutines.common) implementation(Dependency.multiplatform.stately.isolate) implementation(Dependency.multiplatform.stately.concurrency) @@ -48,7 +49,6 @@ kotlin { dependencies { implementation(Dependency.multiplatform.test.common) implementation(Dependency.multiplatform.test.annotations) - implementation(Dependency.multiplatform.coroutines.common) implementation(LocalDependency.antibytes.test.annotations) implementation(LocalDependency.antibytes.test.coroutine) diff --git a/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/ExampleContract.kt b/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/ExampleContract.kt index 8c4b0dce..7838598a 100644 --- a/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/ExampleContract.kt +++ b/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/ExampleContract.kt @@ -6,6 +6,8 @@ package tech.antibytes.kmock.example +import kotlinx.coroutines.flow.SharedFlow + interface ExampleContract { interface SampleDomainObject { var id: String @@ -25,6 +27,6 @@ interface ExampleContract { interface SampleController { suspend fun fetchAndStore(url: String): SampleDomainObject - fun find(id: String): SampleDomainObject + fun find(id: String): SharedFlow } } diff --git a/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/SampleController.kt b/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/SampleController.kt index 64ae2234..62f54210 100644 --- a/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/SampleController.kt +++ b/examples/src/commonMain/kotlin/tech/antibytes/kmock/example/SampleController.kt @@ -6,17 +6,41 @@ package tech.antibytes.kmock.example +import co.touchlab.stately.concurrency.AtomicReference +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch + class SampleController( private val localRepository: ExampleContract.SampleLocalRepository, private val remoteRepository: ExampleContract.SampleRemoteRepository ) : ExampleContract.SampleController { override suspend fun fetchAndStore(url: String): ExampleContract.SampleDomainObject { val domainObject = remoteRepository.fetch(url) + domainObject.id + domainObject.id = "42" return localRepository.store(domainObject.id, domainObject.value) } - override fun find(id: String): ExampleContract.SampleDomainObject { - TODO("Not yet implemented") + override fun find(id: String): SharedFlow { + val ref = AtomicReference(id) + val flow = MutableSharedFlow() + + CoroutineScope(Dispatchers.Default).launch { + localRepository.contains(ref.get()) + + val objc = remoteRepository.find(ref.get()) + + localRepository.fetch(objc.id) + + objc.id = "23" + + flow.emit(objc) + } + + return flow } } diff --git a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerSpec.kt b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerSpec.kt index 9b65caaa..37224078 100644 --- a/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerSpec.kt +++ b/examples/src/commonTest/kotlin/tech/antibytes/kmock/example/SampleControllerSpec.kt @@ -6,24 +6,35 @@ package tech.antibytes.kmock.example +import co.touchlab.stately.concurrency.AtomicReference +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import tech.antibytes.kmock.AsyncFunMockery -import tech.antibytes.kmock.PropertyMockery -import tech.antibytes.kmock.example.ExampleContract.SampleLocalRepository -import tech.antibytes.kmock.example.ExampleContract.SampleRemoteRepository -import tech.antibytes.kmock.example.ExampleContract.SampleDomainObject import tech.antibytes.kmock.KMockContract.Collector +import tech.antibytes.kmock.PropertyMockery import tech.antibytes.kmock.SyncFunMockery import tech.antibytes.kmock.Verifier +import tech.antibytes.kmock.example.ExampleContract.SampleDomainObject +import tech.antibytes.kmock.example.ExampleContract.SampleLocalRepository +import tech.antibytes.kmock.example.ExampleContract.SampleRemoteRepository import tech.antibytes.kmock.verify import tech.antibytes.kmock.verifyOrder import tech.antibytes.kmock.verifyStrictOrder import tech.antibytes.kmock.wasGotten +import tech.antibytes.kmock.wasSet +import tech.antibytes.kmock.wasSetTo import tech.antibytes.kmock.withArguments import tech.antibytes.kmock.withSameArguments +import tech.antibytes.kmock.withoutArguments import tech.antibytes.util.test.coroutine.AsyncTestReturnValue -import tech.antibytes.util.test.coroutine.runBlockingTest +import tech.antibytes.util.test.coroutine.defaultTestContext +import tech.antibytes.util.test.coroutine.runBlockingTestWithTimeout +import tech.antibytes.util.test.coroutine.runBlockingTestWithTimeoutInScope 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 @@ -48,14 +59,14 @@ class SampleControllerSpec { val verifier = Verifier() val url = fixture.fixture() - val id = fixture.fixture() + val id = fixture.listFixture(size = 2) val number = fixture.fixture() val local = SampleLocalRepositoryStub(verifier) val remote = SampleRemoteRepositoryStub(verifier) val domainObject = SampleDomainObjectStub(verifier) - domainObject.propId.get = id + domainObject.propId.getMany = id domainObject.propValue.get = number remote.fetch.returnValue = domainObject @@ -63,36 +74,90 @@ class SampleControllerSpec { // When val controller = SampleController(local, remote) - return runBlockingTest { + return runBlockingTestWithTimeout { val actual = controller.fetchAndStore(url) // Then actual mustBe domainObject verify(exactly = 1) { remote.fetch.withSameArguments(url) } - verify(exactly = 1) { local.store.withSameArguments(id, number) } + verify(exactly = 1) { local.store.withSameArguments(id[1], number) } verifier.verifyStrictOrder { - withArguments(remote.fetch, url) - add(domainObject.propId.wasGotten()) - add(domainObject.propValue.wasGotten()) - withSameArguments(local.store, id, number) + withSameArguments(remote.fetch, url) + wasGotten(domainObject.propId) + wasSet(domainObject.propId) + wasGotten(domainObject.propId) + wasGotten(domainObject.propValue) + withSameArguments(local.store, id[1], number) } verifier.verifyOrder { withArguments(remote.fetch, url) - withSameArguments(local.store, id, number) + wasSetTo(domainObject.propId, "42") + withArguments(local.store, id[1]) } } } -} + @Test + @JsName("fn2") + fun `Given find it fetches a DomainObjects`(): AsyncTestReturnValue { + // Given + val verifier = Verifier() + + val idOrg = fixture.fixture() + val id = fixture.fixture() + val number = fixture.fixture() + + val local = SampleLocalRepositoryStub(verifier) + val remote = SampleRemoteRepositoryStub(verifier) + val domainObject = SampleDomainObjectStub(verifier) + + domainObject.propId.get = id + domainObject.propValue.get = number + + remote.find.returnValue = domainObject + local.contains.sideEffect = { true } + local.fetch.returnValue = domainObject + + // When + val controller = SampleController(local, remote) + val doRef = AtomicReference(domainObject) + val contextRef = AtomicReference(defaultTestContext) + + return runBlockingTestWithTimeoutInScope(defaultTestContext) { + // When + controller.find(idOrg) + .onEach { actual -> actual mustBe doRef.get() } + .launchIn(CoroutineScope(contextRef.get())) + + delay(20) + + verify(exactly = 1) { local.contains.withSameArguments(idOrg) } + verify(exactly = 1) { local.fetch.withSameArguments(id) } + verify(exactly = 1) { remote.find.withSameArguments(idOrg) } + + verifier.verifyStrictOrder { + withSameArguments(local.contains, idOrg) + withSameArguments(remote.find, idOrg) + wasGotten(domainObject.propId) + withSameArguments(local.fetch, id) + wasSet(domainObject.propId) + } + + verifier.verifyOrder { + withoutArguments(local.contains, "abc") + } + } + } +} private class SampleDomainObjectStub( verifier: Collector = Collector { _, _ -> Unit } ) : SampleDomainObject { - val propId = PropertyMockery("id", verifier) - val propValue = PropertyMockery("value", verifier) + val propId = PropertyMockery("do#id", verifier) + val propValue = PropertyMockery("do#value", verifier) override var id: String get() = propId.onGet() @@ -105,8 +170,8 @@ private class SampleDomainObjectStub( private class SampleRemoteRepositoryStub( verifier: Collector = Collector { _, _ -> Unit } ) : SampleRemoteRepository { - val fetch = AsyncFunMockery SampleDomainObject>("fetch", verifier) - val find = SyncFunMockery SampleDomainObject>("find", verifier) + val fetch = AsyncFunMockery SampleDomainObject>("remote#fetch", verifier) + val find = SyncFunMockery SampleDomainObject>("remote#find", verifier) override suspend fun fetch(url: String): SampleDomainObject = fetch.invoke(url) @@ -114,11 +179,11 @@ private class SampleRemoteRepositoryStub( } private class SampleLocalRepositoryStub( - verifier: Collector = Collector { _, _ -> Unit } -): SampleLocalRepository { - val store = AsyncFunMockery SampleDomainObject>("store", verifier) - val contains = SyncFunMockery Boolean>("contains", verifier) - val fetch = SyncFunMockery SampleDomainObject>("fetch", verifier) + verifier: Collector = Collector { _, _ -> Unit } +) : SampleLocalRepository { + val store = AsyncFunMockery SampleDomainObject>("local#store", verifier) + val contains = SyncFunMockery Boolean>("local#contains", verifier) + val fetch = SyncFunMockery SampleDomainObject>("local#fetch", verifier) override suspend fun store(id: String, value: Int): SampleDomainObject = store.invoke(id, value) diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt index 77be5792..d4d384ac 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/KMockContract.kt @@ -334,7 +334,7 @@ interface KMockContract { const val NOTHING_TO_STRICTLY_VERIFY = "The given verification chain (has \$1 items) does not match the captured calls (\$2 were captured)." const val NOTHING_TO_VERIFY = "The given verification chain (has \$1 items) is exceeding the captured calls (\$2 were captured)." const val NO_MATCHING_CALL_IDX = "The captured calls of \$1 exceeds the captured calls." - const val MISMATCHING_FUNCTION = "Excepted \$1, but got \$2." + const val MISMATCHING_FUNCTION = "Excepted '\$1', but got '\$2'." const val MISMATCHING_CALL_IDX = "Excepted the \$1, but the \$2 was referenced." const val CALL_NOT_FOUND = "Last referred invocation of \$1 was not found." } diff --git a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verification.kt b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verification.kt index 1cd7ef24..8a2f8f6f 100644 --- a/kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verification.kt +++ b/kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verification.kt @@ -95,7 +95,7 @@ private fun initChainVerification( private fun guardStrictChain(references: List, handles: List) { if (handles.size != references.size) { - val message = formatMessage(NOTHING_TO_STRICTLY_VERIFY, handles.size, references.size) + val message = formatMessage(NOTHING_TO_STRICTLY_VERIFY, references.size, handles.size) throw AssertionError(message) } @@ -208,6 +208,10 @@ fun Verifier.verifyOrder( guardChain(this.references, handles) this.references.forEach { reference -> + if (handleOffset == handles.size) { + return@forEach + } + val functionName = handles[handleOffset].id val lastCall = handleCalls[functionName] ?: -1 val call = scanHandle(lastCall, handles[handleOffset].callIndices) @@ -216,10 +220,6 @@ fun Verifier.verifyOrder( handleCalls[functionName] = call!! handleOffset += 1 } - - if (handleOffset == handles.size) { - return@forEach - } } ensureAllHandlesAreDone(handles, handleOffset) diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/VerificationSpec.kt b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/VerificationSpec.kt index 4782eb5c..5d16c753 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/kmock/VerificationSpec.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/kmock/VerificationSpec.kt @@ -142,7 +142,7 @@ class VerificationSpec { } } - errorLowerBound.message mustBe "The given verification chain (has ${verifierLower.references.size} items) does not match the captured calls (1 were captured)." + errorLowerBound.message mustBe "The given verification chain (has 1 items) does not match the captured calls (${verifierLower.references.size} were captured)." // Then val errorUpperBound = assertFailsWith { @@ -152,7 +152,7 @@ class VerificationSpec { } } - errorUpperBound.message mustBe "The given verification chain (has ${verifierUpper.references.size} items) does not match the captured calls (1 were captured)." + errorUpperBound.message mustBe "The given verification chain (has 1 items) does not match the captured calls (${verifierUpper.references.size} were captured)." } @Test @@ -185,7 +185,7 @@ class VerificationSpec { } } - error.message mustBe "Excepted ${handleMockery.id}, but got ${referenceMockery.id}." + error.message mustBe "Excepted '${handleMockery.id}', but got '${referenceMockery.id}'." } @Test diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/mock/FunMockeryStub.kt b/kmock/src/commonTest/kotlin/tech/antibytes/mock/FunMockeryStub.kt index ff0adb2c..a4d0e91a 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/mock/FunMockeryStub.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/mock/FunMockeryStub.kt @@ -177,4 +177,8 @@ class FunMockeryStub( ): Any { TODO("Not yet implemented") } + + override fun clear() { + TODO("Not yet implemented") + } } diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/mock/PropertyMockeryStub.kt b/kmock/src/commonTest/kotlin/tech/antibytes/mock/PropertyMockeryStub.kt index 3c58bbdd..34462a3e 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/mock/PropertyMockeryStub.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/mock/PropertyMockeryStub.kt @@ -40,4 +40,8 @@ class PropertyMockeryStub( override fun onSet(value: Any) { TODO("Not yet implemented") } + + override fun clear() { + TODO("Not yet implemented") + } } diff --git a/kmock/src/commonTest/kotlin/tech/antibytes/mock/VerifierStub.kt b/kmock/src/commonTest/kotlin/tech/antibytes/mock/VerifierStub.kt index 52ba1900..a23ac583 100644 --- a/kmock/src/commonTest/kotlin/tech/antibytes/mock/VerifierStub.kt +++ b/kmock/src/commonTest/kotlin/tech/antibytes/mock/VerifierStub.kt @@ -10,4 +10,8 @@ import tech.antibytes.kmock.KMockContract class VerifierStub( override val references: List -) : KMockContract.Verifier +) : KMockContract.Verifier { + override fun clear() { + TODO("Not yet implemented") + } +}