From 69b00aa9e9e48d41b4543a2066d7018d00b078f4 Mon Sep 17 00:00:00 2001 From: Arkadii Ivanov <> Date: Thu, 15 Oct 2020 00:32:19 +0100 Subject: [PATCH] Tests for list module --- .../buildSrc/buildSrc/src/main/kotlin/Deps.kt | 5 + .../kotlin/multiplatform-setup.gradle.kts | 18 +++ .../todo/common/database/build.gradle.kts | 1 + .../database/TestDatabaseDriverFactory.kt | 13 +++ .../database/TestDatabaseDriverFactory.kt | 6 + .../database/TestDatabaseDriverFactory.kt | 13 +++ examples/todo/common/list/build.gradle.kts | 8 ++ .../common/list/integration/TodoListImpl.kt | 7 +- .../common/list/integration/TodoListTest.kt | 103 ++++++++++++++++++ .../list/store/TestTodoListStoreDatabase.kt | 29 +++++ .../common/list/store/TodoListStoreTest.kt | 58 ++++++++++ 11 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 examples/todo/common/database/src/androidMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt create mode 100644 examples/todo/common/database/src/commonMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt create mode 100644 examples/todo/common/database/src/desktopMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt create mode 100644 examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/integration/TodoListTest.kt create mode 100644 examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/store/TestTodoListStoreDatabase.kt create mode 100644 examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/store/TodoListStoreTest.kt diff --git a/examples/todo/buildSrc/buildSrc/src/main/kotlin/Deps.kt b/examples/todo/buildSrc/buildSrc/src/main/kotlin/Deps.kt index e3830a5d81..d390ed7533 100644 --- a/examples/todo/buildSrc/buildSrc/src/main/kotlin/Deps.kt +++ b/examples/todo/buildSrc/buildSrc/src/main/kotlin/Deps.kt @@ -4,6 +4,9 @@ object Deps { object Kotlin { private const val VERSION = "1.4.0" const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$VERSION" + const val testCommon = "org.jetbrains.kotlin:kotlin-test-common:$VERSION" + const val testJunit = "org.jetbrains.kotlin:kotlin-test-junit:$VERSION" + const val testAnnotationsCommon = "org.jetbrains.kotlin:kotlin-test-annotations-common:$VERSION" } object Compose { @@ -40,6 +43,8 @@ object Deps { object Reaktive { private const val VERSION = "1.1.18" const val reaktive = "com.badoo.reaktive:reaktive:$VERSION" + const val reaktiveTesting = "com.badoo.reaktive:reaktive-testing:$VERSION" + const val utils = "com.badoo.reaktive:utils:$VERSION" const val coroutinesInterop = "com.badoo.reaktive:coroutines-interop:$VERSION" } } diff --git a/examples/todo/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts b/examples/todo/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts index e2ee8bc8ec..75322bcdf3 100644 --- a/examples/todo/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts +++ b/examples/todo/buildSrc/src/main/kotlin/multiplatform-setup.gradle.kts @@ -16,6 +16,24 @@ kotlin { implementation(Deps.Badoo.Reaktive.reaktive) } } + + named("commonTest") { + dependencies { + implementation(Deps.JetBrains.Kotlin.testCommon) + implementation(Deps.JetBrains.Kotlin.testAnnotationsCommon) + } + } + + named("androidTest") { + dependencies { + implementation(Deps.JetBrains.Kotlin.testJunit) + } + } + named("desktopTest") { + dependencies { + implementation(Deps.JetBrains.Kotlin.testJunit) + } + } } tasks.withType { diff --git a/examples/todo/common/database/build.gradle.kts b/examples/todo/common/database/build.gradle.kts index a9667cc5be..467afcdbf3 100755 --- a/examples/todo/common/database/build.gradle.kts +++ b/examples/todo/common/database/build.gradle.kts @@ -20,6 +20,7 @@ kotlin { androidMain { dependencies { implementation(Deps.Squareup.SQLDelight.androidDriver) + implementation(Deps.Squareup.SQLDelight.sqliteDriver) } } diff --git a/examples/todo/common/database/src/androidMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt b/examples/todo/common/database/src/androidMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt new file mode 100644 index 0000000000..4eab94f227 --- /dev/null +++ b/examples/todo/common/database/src/androidMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt @@ -0,0 +1,13 @@ +package example.todo.common.database + +import com.squareup.sqldelight.db.SqlDriver +import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver +import example.todo.database.TodoDatabase + +@Suppress("FunctionName") // FactoryFunction +actual fun TestDatabaseDriver(): SqlDriver { + val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) + TodoDatabase.Schema.create(driver) + + return driver +} diff --git a/examples/todo/common/database/src/commonMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt b/examples/todo/common/database/src/commonMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt new file mode 100644 index 0000000000..8181058503 --- /dev/null +++ b/examples/todo/common/database/src/commonMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt @@ -0,0 +1,6 @@ +package example.todo.common.database + +import com.squareup.sqldelight.db.SqlDriver + +@Suppress("FunctionName") +expect fun TestDatabaseDriver(): SqlDriver diff --git a/examples/todo/common/database/src/desktopMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt b/examples/todo/common/database/src/desktopMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt new file mode 100644 index 0000000000..4eab94f227 --- /dev/null +++ b/examples/todo/common/database/src/desktopMain/kotlin/example/todo/common/database/TestDatabaseDriverFactory.kt @@ -0,0 +1,13 @@ +package example.todo.common.database + +import com.squareup.sqldelight.db.SqlDriver +import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver +import example.todo.database.TodoDatabase + +@Suppress("FunctionName") // FactoryFunction +actual fun TestDatabaseDriver(): SqlDriver { + val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) + TodoDatabase.Schema.create(driver) + + return driver +} diff --git a/examples/todo/common/list/build.gradle.kts b/examples/todo/common/list/build.gradle.kts index 5d73f677cb..c9543f35a3 100755 --- a/examples/todo/common/list/build.gradle.kts +++ b/examples/todo/common/list/build.gradle.kts @@ -15,5 +15,13 @@ kotlin { implementation(Deps.Badoo.Reaktive.reaktive) } } + + named("commonTest") { + dependencies { + implementation(Deps.ArkIvanov.MVIKotlin.mvikotlinMain) + implementation(Deps.Badoo.Reaktive.reaktiveTesting) + implementation(Deps.Badoo.Reaktive.utils) + } + } } } diff --git a/examples/todo/common/list/src/commonMain/kotlin/example/todo/common/list/integration/TodoListImpl.kt b/examples/todo/common/list/src/commonMain/kotlin/example/todo/common/list/integration/TodoListImpl.kt index 059be67197..f16f1908da 100644 --- a/examples/todo/common/list/src/commonMain/kotlin/example/todo/common/list/integration/TodoListImpl.kt +++ b/examples/todo/common/list/src/commonMain/kotlin/example/todo/common/list/integration/TodoListImpl.kt @@ -19,6 +19,7 @@ import com.arkivanov.decompose.ComponentContext import example.todo.common.list.TodoList import example.todo.common.list.TodoList.Dependencies import example.todo.common.list.TodoList.Output +import example.todo.common.list.store.TodoListStore import example.todo.common.list.store.TodoListStore.Intent import example.todo.common.list.store.TodoListStoreProvider import example.todo.common.utils.composeState @@ -37,6 +38,8 @@ internal class TodoListImpl( ).provide() } + internal val state: TodoListStore.State get() = store.state + @Composable override fun invoke() { val state by store.composeState @@ -62,11 +65,11 @@ internal class TodoListImpl( } } - private fun onItemClicked(id: Long) { + internal fun onItemClicked(id: Long) { listOutput.onNext(Output.ItemSelected(id = id)) } - private fun onDoneChanged(id: Long, isDone: Boolean) { + internal fun onDoneChanged(id: Long, isDone: Boolean) { store.accept(Intent.SetDone(id = id, isDone = isDone)) } } diff --git a/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/integration/TodoListTest.kt b/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/integration/TodoListTest.kt new file mode 100644 index 0000000000..2e72588f54 --- /dev/null +++ b/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/integration/TodoListTest.kt @@ -0,0 +1,103 @@ +package example.todo.common.list.integration + +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.decompose.lifecycle.LifecycleRegistry +import com.arkivanov.mvikotlin.core.store.StoreFactory +import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory +import com.badoo.reaktive.base.Consumer +import com.badoo.reaktive.scheduler.overrideSchedulers +import com.badoo.reaktive.subject.publish.PublishSubject +import com.badoo.reaktive.test.observable.assertValue +import com.badoo.reaktive.test.observable.test +import com.badoo.reaktive.test.scheduler.TestScheduler +import example.todo.common.database.TestDatabaseDriver +import example.todo.common.list.TodoList.Dependencies +import example.todo.common.list.TodoList.Output +import example.todo.common.list.store.TodoItem +import example.todo.database.TodoDatabase +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@Suppress("TestFunctionName") +class TodoListTest { + + private val lifecycle = LifecycleRegistry() + private val database = TodoDatabase(TestDatabaseDriver()) + private val outputSubject = PublishSubject() + private val output = outputSubject.test() + + private val queries = database.todoDatabaseQueries + + private val impl by lazy { + TodoListImpl( + componentContext = DefaultComponentContext(lifecycle = lifecycle), + dependencies = object : Dependencies { + override val storeFactory: StoreFactory = DefaultStoreFactory + override val database: TodoDatabase = this@TodoListTest.database + override val listOutput: Consumer = outputSubject + } + ) + } + + @BeforeTest + fun before() { + overrideSchedulers( + main = { TestScheduler() }, + io = { TestScheduler() } + ) + } + + @Test + fun WHEN_item_added_to_database_THEN_item_displayed() { + queries.add("Item1") + + assertEquals("Item1", firstItem().text) + } + + @Test + fun WHEN_item_clicked_THEN_Output_ItemSelected_emitted() { + queries.add("Item1") + val id = firstItem().id + + impl.onItemClicked(id = id) + + output.assertValue(Output.ItemSelected(id = id)) + } + + @Test + fun GIVEN_item_isDone_false_WHEN_done_changed_to_true_THEN_item_isDone_true() { + queries.add("Item1") + val id = firstItem().id + queries.setDone(id = id, isDone = false) + + impl.onDoneChanged(id = id, isDone = true) + + assertTrue(firstItem().isDone) + } + + @Test + fun GIVEN_item_isDone_true_WHEN_done_changed_to_false_THEN_item_isDone_false() { + queries.add("Item1") + val id = firstItem().id + queries.setDone(id = id, isDone = true) + + impl.onDoneChanged(id = id, isDone = false) + + assertFalse(firstItem().isDone) + } + + @Test + fun WHEN_item_text_changed_THEN_item_updated() { + queries.add("Item1") + val id = firstItem().id + + queries.setText(id = id, text = "New text") + + assertEquals("New text", firstItem().text) + } + + private fun firstItem(): TodoItem = impl.state.items[0] +} diff --git a/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/store/TestTodoListStoreDatabase.kt b/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/store/TestTodoListStoreDatabase.kt new file mode 100644 index 0000000000..6875b24050 --- /dev/null +++ b/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/store/TestTodoListStoreDatabase.kt @@ -0,0 +1,29 @@ +package example.todo.common.list.store + +import com.badoo.reaktive.completable.Completable +import com.badoo.reaktive.completable.completableFromFunction +import com.badoo.reaktive.observable.Observable +import com.badoo.reaktive.subject.behavior.BehaviorSubject +import example.todo.common.list.store.TodoListStoreProvider.Database + +internal class TestTodoListStoreDatabase : Database { + + private val subject = BehaviorSubject>(emptyList()) + + var items: List + get() = subject.value + set(value) { + subject.onNext(value) + } + + override val updates: Observable> = subject + + override fun setDone(id: Long, isDone: Boolean): Completable = + completableFromFunction { + update(id = id) { copy(isDone = isDone) } + } + + private fun update(id: Long, func: TodoItem.() -> TodoItem) { + items = items.map { if (it.id == id) it.func() else it } + } +} diff --git a/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/store/TodoListStoreTest.kt b/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/store/TodoListStoreTest.kt new file mode 100644 index 0000000000..637d2becc7 --- /dev/null +++ b/examples/todo/common/list/src/commonTest/kotlin/example/todo/common/list/store/TodoListStoreTest.kt @@ -0,0 +1,58 @@ +package example.todo.common.list.store + +import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory +import com.badoo.reaktive.scheduler.overrideSchedulers +import com.badoo.reaktive.test.scheduler.TestScheduler +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +@Suppress("TestFunctionName") +class TodoListStoreTest { + + private val database = TestTodoListStoreDatabase() + private val provider = TodoListStoreProvider(storeFactory = DefaultStoreFactory, database = database) + + @BeforeTest + fun before() { + overrideSchedulers(main = { TestScheduler() }) + } + + @Test + fun GIVEN_items_in_database_WHEN_created_THEN_loads_items_from_database() { + val item1 = TodoItem(id = 1L, order = 2L, text = "item1") + val item2 = TodoItem(id = 2L, order = 1L, text = "item2", isDone = true) + val item3 = TodoItem(id = 3L, order = 3L, text = "item3") + database.items = listOf(item1, item2, item3) + + val store = provider.provide() + + assertEquals(listOf(item3, item1, item2), store.state.items) + } + + @Test + fun WHEN_items_changed_in_database_THEN_contains_new_items() { + database.items = listOf(TodoItem()) + val store = provider.provide() + + val item1 = TodoItem(id = 1L, order = 2L, text = "item1") + val item2 = TodoItem(id = 2L, order = 1L, text = "item2", isDone = true) + val item3 = TodoItem(id = 3L, order = 3L, text = "item3") + database.items = listOf(item1, item2, item3) + + assertEquals(listOf(item3, item1, item2), store.state.items) + } + + @Test + fun WHEN_Intent_setDone_THEN_done_changed_in_state() { + val item1 = TodoItem(id = 1L, text = "item1") + val item2 = TodoItem(id = 2L, text = "item2", isDone = false) + database.items = listOf(item1, item2) + val store = provider.provide() + + store.accept(TodoListStore.Intent.SetDone(id = 2L, isDone = true)) + + assertTrue(store.state.items.first { it.id == 2L }.isDone) + } +}