From 2f30b7741aab1542b72aa73a1d97e9236b328448 Mon Sep 17 00:00:00 2001 From: OmkarTenkale Date: Sat, 27 Jul 2024 18:52:24 +0530 Subject: [PATCH] add tests --- gradle/libs.versions.toml | 2 + nodal/build.gradle.kts | 2 + .../nodal/NodeDependenciesTest.kt | 86 +++++++++++++ .../kotlin/dev/omkartenkale/nodal/NodeTest.kt | 120 ++++++++++++++++++ .../nodal/util/MainCoroutineRule.kt | 31 +++++ .../kotlin/dev.omkartenkale.nodal/Node.kt | 2 +- 6 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/NodeDependenciesTest.kt create mode 100644 nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/NodeTest.kt create mode 100644 nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/util/MainCoroutineRule.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dcfb175..85b0158 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -35,6 +35,8 @@ ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-koin = { module = "cafe.adriel.voyager:voyager-koin", version.ref = "voyager" } kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" } +coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlin-coroutines" } +mockk = { module = "io.mockk:mockk", version = "1.13.12" } kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "kotlinx-serialization" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" } kotlinx-atomicfu = { group = "org.jetbrains.kotlinx", name = "atomicfu", version.ref = "kotlinx-atomicfu" } diff --git a/nodal/build.gradle.kts b/nodal/build.gradle.kts index 96614d6..d69f735 100644 --- a/nodal/build.gradle.kts +++ b/nodal/build.gradle.kts @@ -54,6 +54,8 @@ kotlin { val commonTest by getting { dependencies { implementation(libs.kotlin.test) + implementation(libs.coroutines.test) + implementation(libs.mockk) } } } diff --git a/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/NodeDependenciesTest.kt b/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/NodeDependenciesTest.kt new file mode 100644 index 0000000..cd54300 --- /dev/null +++ b/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/NodeDependenciesTest.kt @@ -0,0 +1,86 @@ +package dev.omkartenkale.nodal + +import dev.omkartenkale.nodal.plugin.NodalPlugins +import dev.omkartenkale.nodal.util.MainDispatcherRule +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.currentTime +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.yield +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertFails +import kotlin.test.assertFailsWith +import kotlin.test.fail + +class NodeDependenciesTest { + + @get:Rule + val coroutineRule = MainDispatcherRule() + + @Before + fun setUp() { + + } + + @Test + fun `verify node provides dependencies`() = runTest { + class Some + class ANode: Node() + + class RootNode : Node() { + override val providesDependencies: DependencyDeclaration = { + provides { Some() } + } + } + + val rootNode = Node.createRootNode( + klass = RootNode::class, + onRequestRemove = { }) {} as RootNode + + val some = rootNode.addChild().dependencies.getOrNull() + assert(some != null) + } + + @Test + fun `verify eager instances are created`() = runTest { + class Some + class CustomInstantiationException: Exception() + + class RootNode : Node() { + val some: Some by dependencies() + + override val providesDependencies: DependencyDeclaration = { + provides { + throw CustomInstantiationException() + Some() + } + } + } + + assertFails { + Node.createRootNode( + klass = RootNode::class, + nodalConfig = NodalConfig(createEagerInstances = true), + onRequestRemove = { }) {} as RootNode + } + + try { + Node.createRootNode( + klass = RootNode::class, + nodalConfig = NodalConfig(createEagerInstances = false), + onRequestRemove = { }) {} as RootNode + + }catch (e:Exception){ + var cause: Throwable? = e + while (cause!= null){ + if(e is CustomInstantiationException) { + fail("Un-eager instance created eagerly") + } + cause = e.cause + } + } + } + + +} \ No newline at end of file diff --git a/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/NodeTest.kt b/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/NodeTest.kt new file mode 100644 index 0000000..3e15c48 --- /dev/null +++ b/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/NodeTest.kt @@ -0,0 +1,120 @@ +package dev.omkartenkale.nodal + +import dev.omkartenkale.nodal.Node.Companion.ui +import dev.omkartenkale.nodal.util.MainDispatcherRule +import dev.omkartenkale.nodal.util.doOnAdded +import dev.omkartenkale.nodal.util.doOnRemoved +import dev.omkartenkale.nodal.util.isAdded +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.isActive +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import kotlin.test.assertFails + +class NodeTest { + class RootNode : Node() + class ANode : Node() + class BNode : Node() + + private lateinit var rootNode: RootNode + + @get:Rule + val coroutineRule = MainDispatcherRule() + + @Before + fun setUp() { + rootNode = Node.createRootNode( + klass = RootNode::class, + nodalConfig = NodalConfig(false), + onRequestRemove = { }) {} as RootNode + } + + @Test + fun `verify node addition`() = runTest { + val nodeA = rootNode.addChild() + assert(rootNode.children.contains(nodeA)) + assert(nodeA.isAdded) + + val nodeB = rootNode.addChild() + assert(rootNode.children.contains(nodeB)) + assert(nodeB.isAdded) + assert(rootNode.children.size == 2) + } + + @Test + fun `verify node removal`() = runTest { + val nodeA = rootNode.addChild() + nodeA.removeSelf() + assert(nodeA.isAdded.not()) + assert(rootNode.children.contains(nodeA).not()) + } + + @Test + fun `verify node scope cancelled after removed`() = runTest { + val nodeA = rootNode.addChild() + nodeA.removeSelf() + assert(nodeA.coroutineScope.isActive.not()) + } + + @Test + fun `verify node added callback is invoked`() = runTest { + val callback = mockk<() -> Unit>(relaxed = true) + val nodeA = rootNode.addChild() + nodeA.doOnAdded { + callback() + } + verify { callback.invoke() } + } + + @Test + fun `verify node isDead property is false before removed`() = runTest { + val nodeA = rootNode.addChild() + assert(nodeA.isDead.not()) + } + + @Test + fun `verify node isDead property is updated after removed`() = runTest { + val nodeA = rootNode.addChild() + nodeA.removeSelf() + assert(nodeA.isDead) + } + + @Test + fun `verify node removed callback is invoked`() = runTest { + val callback = mockk<() -> Unit>(relaxed = true) + val nodeA = rootNode.addChild() + nodeA.doOnRemoved { + callback() + } + nodeA.removeSelf() + verify { callback.invoke() } + } + + @Test + fun `verify node doOnInit callback is invoked`() = runTest { + class SomeNode : Node() { + var doOnInitCalled = false + + init { + doOnInit { + doOnInitCalled = true + } + } + } + rootNode.addChild().apply { + assert(doOnInitCalled) + } + } + + @Test + fun `verify node does not have ui dependency in unit tests`() = runTest { + val nodeA = rootNode.addChild() + assertFails { + nodeA.ui + } + } + +} \ No newline at end of file diff --git a/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/util/MainCoroutineRule.kt b/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/util/MainCoroutineRule.kt new file mode 100644 index 0000000..f0f9767 --- /dev/null +++ b/nodal/src/androidUnitTest/kotlin/dev/omkartenkale/nodal/util/MainCoroutineRule.kt @@ -0,0 +1,31 @@ +package dev.omkartenkale.nodal.util + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestCoroutineDispatcher +import kotlinx.coroutines.test.TestCoroutineScope +import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.setMain +import org.junit.rules.TestWatcher +import org.junit.runner.Description + + +class MainDispatcherRule @OptIn(ExperimentalCoroutinesApi::class) constructor( + val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(), +) : TestWatcher() { + + @OptIn(ExperimentalCoroutinesApi::class) + override fun starting(description: Description) { + super.starting(description) + Dispatchers.setMain(testDispatcher) + } + + @OptIn(ExperimentalCoroutinesApi::class) + override fun finished(description: Description) { + super.finished(description) + Dispatchers.resetMain() + } +} \ No newline at end of file diff --git a/nodal/src/commonMain/kotlin/dev.omkartenkale.nodal/Node.kt b/nodal/src/commonMain/kotlin/dev.omkartenkale.nodal/Node.kt index d0ef40e..f063e0e 100644 --- a/nodal/src/commonMain/kotlin/dev.omkartenkale.nodal/Node.kt +++ b/nodal/src/commonMain/kotlin/dev.omkartenkale.nodal/Node.kt @@ -181,7 +181,7 @@ public open class Node { public fun createRootNode( klass: KClass, - nodalConfig: NodalConfig, + nodalConfig: NodalConfig = NodalConfig(), onRequestRemove: (Node) -> Unit, plugins: List = emptyList(), dependencyDeclaration: DependencyDeclaration,