-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.nek12.flowMVI | ||
|
||
import io.kotest.common.ExperimentalKotest | ||
import io.kotest.core.config.AbstractProjectConfig | ||
|
||
class CoreTestConfig : AbstractProjectConfig() { | ||
@OptIn(ExperimentalKotest::class) | ||
override var testCoroutineDispatcher = true | ||
|
||
override val coroutineDebugProbes: Boolean = true | ||
override val invocationTimeout = 5000L | ||
override val parallelism: Int = 1 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import TestState.Some | ||
import TestState.SomeData | ||
import app.cash.turbine.test | ||
import com.nek12.flowMVI.ActionShareBehavior.DISTRIBUTE | ||
import com.nek12.flowMVI.ActionShareBehavior.RESTRICT | ||
import com.nek12.flowMVI.ActionShareBehavior.SHARE | ||
import com.nek12.flowMVI.MVIStore | ||
import com.nek12.flowMVI.MVISubscriber | ||
import com.nek12.flowMVI.TestStore | ||
import com.nek12.flowMVI.subscribe | ||
import io.kotest.assertions.throwables.shouldThrowAny | ||
import io.kotest.assertions.throwables.shouldThrowExactly | ||
import io.kotest.core.spec.style.FreeSpec | ||
import io.kotest.core.test.testCoroutineScheduler | ||
import io.kotest.matchers.shouldBe | ||
import io.mockk.coVerify | ||
import io.mockk.mockk | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
import kotlinx.coroutines.NonCancellable.cancel | ||
import kotlinx.coroutines.coroutineScope | ||
import kotlinx.coroutines.joinAll | ||
import kotlinx.coroutines.test.TestCoroutineScheduler | ||
import kotlinx.coroutines.test.TestScope | ||
import kotlinx.coroutines.test.UnconfinedTestDispatcher | ||
import kotlinx.coroutines.test.advanceUntilIdle | ||
import kotlinx.coroutines.test.runCurrent | ||
import util.launched | ||
|
||
|
||
@OptIn(ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class) | ||
class StoreTest: FreeSpec({ | ||
coroutineTestScope = true | ||
blockingTest = true | ||
concurrency = 1 | ||
|
||
"given store created" - { | ||
val state = Some | ||
val store = TestStore(state, RESTRICT) { SomeData("data") } | ||
"then state is ${state::class.simpleName}" { | ||
store.states.value shouldBe state | ||
} | ||
"then no actions" { | ||
store.actions.test { | ||
expectNoEvents() | ||
} | ||
} | ||
"then can be launched" - { | ||
var job = store.launch(this) | ||
|
||
"and can't be launched twice" { | ||
shouldThrowExactly<IllegalArgumentException> { | ||
store.launch(this) | ||
} | ||
} | ||
"and can be canceled" { | ||
job.cancel() | ||
job.join() | ||
} | ||
"and can be launched again" { | ||
job = store.launch(this) | ||
job.cancel() | ||
job.join() | ||
} | ||
job.cancel() | ||
} | ||
} | ||
|
||
"given store that sends actions and updates states" - { | ||
val state = SomeData("data") | ||
|
||
val reduce: suspend MVIStore<TestState, TestIntent, TestAction>.(TestIntent) -> TestState = { | ||
send(TestAction.Some) | ||
state | ||
} | ||
"and 2 subscribers" - { | ||
|
||
|
||
"and action type is RESTRICT" - { | ||
"then throws" { | ||
//ensure scope is enclosed, otherwise exception will be thrown outside of assertion | ||
shouldThrowAny { | ||
coroutineScope { | ||
TestStore(Some, RESTRICT, reduce = reduce).launched(this@coroutineScope) { | ||
subscribe(this@coroutineScope, {}, {}) | ||
subscribe(this@coroutineScope, {}, {}) | ||
testCoroutineScheduler.advanceUntilIdle() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
"and action type is DISTRIBUTE" - { | ||
//todo: figure out what does kotest do wrong with the scope, that the subs don't work | ||
val scope = TestScope(testCoroutineScheduler) | ||
val sub1 = mockk<MVISubscriber<TestState, TestAction>>() | ||
val sub2 = mockk<MVISubscriber<TestState, TestAction>>() | ||
TestStore(Some, DISTRIBUTE, reduce = reduce).launched(scope) { | ||
sub1.subscribe(this, scope) | ||
sub2.subscribe(this, scope) | ||
"and intent received" - { | ||
send(TestIntent.Some) | ||
scope.advanceUntilIdle() | ||
"then one subscriber received action only" { | ||
coVerify(exactly = 1) { sub1.consume(TestAction.Some) } | ||
coVerify(exactly = 0) { sub2.consume(TestAction.Some) } | ||
|
||
} | ||
// "then all subscribers updated state" { | ||
// coVerify(exactly = 1) { sub1.render(ofType<SomeData>()) } | ||
// coVerify(exactly = 1) { sub2.render(ofType<SomeData>()) } | ||
// } | ||
} | ||
} | ||
} | ||
|
||
"and action type is SHARE" - { | ||
val scope = testScope | ||
val sub1 = mockk<MVISubscriber<TestState, TestAction>>() | ||
val sub2 = mockk<MVISubscriber<TestState, TestAction>>() | ||
TestStore(Some, SHARE, reduce = reduce).launched(scope) { | ||
sub1.subscribe(this@launched, scope) | ||
sub2.subscribe(this@launched, scope) | ||
"and intent received" - { | ||
send(TestIntent.Some) | ||
"then all subscribers received an action" { | ||
//todo: works, but because of scope does not arrive properly | ||
// coVerify(exactly = 1) { sub1.consume(TestAction.Some) } | ||
// coVerify(exactly = 1) { sub2.consume(TestAction.Some) } | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import com.nek12.flowMVI.MVIAction | ||
import com.nek12.flowMVI.MVIIntent | ||
import com.nek12.flowMVI.MVIState | ||
|
||
sealed class TestState: MVIState { | ||
object Some: TestState() | ||
data class SomeData(val data: String): TestState() | ||
} | ||
|
||
sealed class TestAction: MVIAction { | ||
object Some: TestAction() | ||
data class SomeData(val data: String): TestAction() | ||
} | ||
|
||
sealed class TestIntent: MVIIntent { | ||
object Some: TestIntent() | ||
data class SomeData(val data: String): TestIntent() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
@file:Suppress("TestFunctionName") | ||
|
||
package com.nek12.flowMVI | ||
|
||
import TestAction | ||
import TestIntent | ||
import TestState | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.emptyFlow | ||
|
||
internal fun TestStore( | ||
initialState: TestState, | ||
behavior: ActionShareBehavior, | ||
recover: MVIStore<TestState, TestIntent, TestAction>.(e: Exception) -> TestState = { throw it }, | ||
reduce: suspend MVIStore<TestState, TestIntent, TestAction>.(TestIntent) -> TestState, | ||
) = MVIStore(initialState, behavior, recover, reduce) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package util | ||
|
||
import com.nek12.flowMVI.MVIAction | ||
import com.nek12.flowMVI.MVIIntent | ||
import com.nek12.flowMVI.MVIState | ||
import com.nek12.flowMVI.MVIStore | ||
import io.kotest.core.test.testCoroutineScheduler | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
|
||
suspend inline fun <S: MVIState, I: MVIIntent, A: MVIAction> | ||
MVIStore<S, I, A>.launched(scope: CoroutineScope, block: MVIStore<S, I, A>.() -> Unit) = launch(scope).apply { | ||
block() | ||
cancel() | ||
join() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package util | ||
|
||
import com.nek12.flowMVI.MVIAction | ||
import com.nek12.flowMVI.MVIProvider | ||
import com.nek12.flowMVI.MVIState | ||
import com.nek12.flowMVI.MVISubscriber | ||
import com.nek12.flowMVI.subscribe | ||
import kotlinx.coroutines.CoroutineScope | ||
|
||
class TestSubscriber<S: MVIState, A: MVIAction>( | ||
val render: (S) -> Unit = {}, | ||
val consume: (A) -> Unit = {}, | ||
): MVISubscriber<S, A> { | ||
|
||
var counter = 0 | ||
private set | ||
|
||
private val _states = mutableListOf<S>() | ||
val states: List<S> get() = _states | ||
|
||
private val _actions = mutableListOf<A>() | ||
val actions: List<A> get() = _actions | ||
|
||
|
||
override fun render(state: S) { | ||
++counter | ||
_states.add(state) | ||
render.invoke(state) | ||
} | ||
|
||
override fun consume(action: A) { | ||
++counter | ||
_actions.add(action) | ||
consume.invoke(action) | ||
} | ||
|
||
fun reset() { | ||
_states.clear() | ||
_actions.clear() | ||
} | ||
|
||
suspend inline fun subscribed(provider: MVIProvider<S, *, A>, scope: CoroutineScope, test: TestSubscriber<S, A>.() -> Unit) = | ||
subscribe(provider, scope).apply { | ||
test() | ||
cancel() | ||
join() | ||
reset() | ||
} | ||
} |