Skip to content

Commit

Permalink
added wip tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Nek-12 committed Jun 7, 2022
1 parent dcf786a commit a740dfb
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 0 deletions.
13 changes: 13 additions & 0 deletions core/src/test/kotlin/com/nek12/flowMVI/CoreTestConfig.kt
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
}
136 changes: 136 additions & 0 deletions core/src/test/kotlin/com/nek12/flowMVI/StoreTest.kt
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) }
}
}
}
}
}
}
})
18 changes: 18 additions & 0 deletions core/src/test/kotlin/com/nek12/flowMVI/TestContract.kt
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()
}
16 changes: 16 additions & 0 deletions core/src/test/kotlin/com/nek12/flowMVI/TestStore.kt
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)
16 changes: 16 additions & 0 deletions core/src/test/kotlin/util/StoreTestExt.kt
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()
}
49 changes: 49 additions & 0 deletions core/src/test/kotlin/util/TestSubscriber.kt
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()
}
}

0 comments on commit a740dfb

Please sign in to comment.