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") + } +}