Skip to content

Commit

Permalink
Adjust for heavy coroutines
Browse files Browse the repository at this point in the history
  • Loading branch information
bitPogo committed Feb 11, 2022
1 parent 0bd1174 commit 166d02e
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 38 deletions.
2 changes: 1 addition & 1 deletion examples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package tech.antibytes.kmock.example

import kotlinx.coroutines.flow.SharedFlow

interface ExampleContract {
interface SampleDomainObject {
var id: String
Expand All @@ -25,6 +27,6 @@ interface ExampleContract {

interface SampleController {
suspend fun fetchAndStore(url: String): SampleDomainObject
fun find(id: String): SampleDomainObject
fun find(id: String): SharedFlow<SampleDomainObject>
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExampleContract.SampleDomainObject> {
val ref = AtomicReference(id)
val flow = MutableSharedFlow<ExampleContract.SampleDomainObject>()

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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -48,51 +59,105 @@ class SampleControllerSpec {
val verifier = Verifier()

val url = fixture.fixture<String>()
val id = fixture.fixture<String>()
val id = fixture.listFixture<String>(size = 2)
val number = fixture.fixture<Int>()

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
local.store.returnValue = domainObject

// 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<String>()
val id = fixture.fixture<String>()
val number = fixture.fixture<Int>()

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<String>("id", verifier)
val propValue = PropertyMockery<Int>("value", verifier)
val propId = PropertyMockery<String>("do#id", verifier)
val propValue = PropertyMockery<Int>("do#value", verifier)

override var id: String
get() = propId.onGet()
Expand All @@ -105,20 +170,20 @@ private class SampleDomainObjectStub(
private class SampleRemoteRepositoryStub(
verifier: Collector = Collector { _, _ -> Unit }
) : SampleRemoteRepository {
val fetch = AsyncFunMockery<SampleDomainObject, suspend (String) -> SampleDomainObject>("fetch", verifier)
val find = SyncFunMockery<SampleDomainObject, (String) -> SampleDomainObject>("find", verifier)
val fetch = AsyncFunMockery<SampleDomainObject, suspend (String) -> SampleDomainObject>("remote#fetch", verifier)
val find = SyncFunMockery<SampleDomainObject, (String) -> SampleDomainObject>("remote#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, suspend (String, Int) -> SampleDomainObject>("store", verifier)
val contains = SyncFunMockery<Boolean, (String) -> Boolean>("contains", verifier)
val fetch = SyncFunMockery<SampleDomainObject, (String) -> SampleDomainObject>("fetch", verifier)
verifier: Collector = Collector { _, _ -> Unit }
) : SampleLocalRepository {
val store = AsyncFunMockery<SampleDomainObject, suspend (String, Int) -> SampleDomainObject>("local#store", verifier)
val contains = SyncFunMockery<Boolean, (String) -> Boolean>("local#contains", verifier)
val fetch = SyncFunMockery<SampleDomainObject, (String) -> SampleDomainObject>("local#fetch", verifier)

override suspend fun store(id: String, value: Int): SampleDomainObject = store.invoke(id, value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
Expand Down
10 changes: 5 additions & 5 deletions kmock/src/commonMain/kotlin/tech/antibytes/kmock/Verification.kt
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ private fun initChainVerification(

private fun guardStrictChain(references: List<Reference>, handles: List<VerificationHandle>) {
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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -216,10 +220,6 @@ fun Verifier.verifyOrder(
handleCalls[functionName] = call!!
handleOffset += 1
}

if (handleOffset == handles.size) {
return@forEach
}
}

ensureAllHandlesAreDone(handles, handleOffset)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AssertionError> {
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,8 @@ class FunMockeryStub(
): Any {
TODO("Not yet implemented")
}

override fun clear() {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ class PropertyMockeryStub(
override fun onSet(value: Any) {
TODO("Not yet implemented")
}

override fun clear() {
TODO("Not yet implemented")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ import tech.antibytes.kmock.KMockContract

class VerifierStub(
override val references: List<KMockContract.Reference>
) : KMockContract.Verifier
) : KMockContract.Verifier {
override fun clear() {
TODO("Not yet implemented")
}
}

0 comments on commit 166d02e

Please sign in to comment.