From a9d192fa399c6bddcc6d3bdede485f64f7765a7e Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 26 Oct 2021 18:09:07 +0200 Subject: [PATCH] Flow migration: add back some test --- dependencies.gradle | 3 +- vector/build.gradle | 2 + .../quads/SharedSecureStorageViewModelTest.kt | 110 +++++++++++------- .../java/im/vector/app/test/Extensions.kt | 22 ++-- .../im/vector/app/test/FlowTestObserver.kt | 53 +++++++++ 5 files changed, 138 insertions(+), 52 deletions(-) create mode 100644 vector/src/test/java/im/vector/app/test/FlowTestObserver.kt diff --git a/dependencies.gradle b/dependencies.gradle index 776e89f9b3..15660f17f2 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -41,7 +41,8 @@ ext.libs = [ jetbrains : [ 'coroutinesCore' : "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinCoroutines", 'coroutinesAndroid' : "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlinCoroutines", - 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines" + 'coroutinesRx2' : "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$kotlinCoroutines", + 'coroutinesTest' : "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutines" ], androidx : [ 'appCompat' : "androidx.appcompat:appcompat:1.3.1", diff --git a/vector/build.gradle b/vector/build.gradle index a5d10e222b..493a26dcc6 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -508,6 +508,7 @@ dependencies { // Plant Timber tree for test testImplementation libs.tests.timberJunitRule testImplementation libs.airbnb.mavericksTesting + testImplementation libs.jetbrains.coroutinesTest // Activate when you want to check for leaks, from time to time. //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3' @@ -521,6 +522,7 @@ dependencies { androidTestImplementation libs.androidx.espressoIntents androidTestImplementation libs.tests.kluent androidTestImplementation libs.androidx.coreTesting + androidTestImplementation libs.jetbrains.coroutinesTest // Plant Timber tree for test androidTestImplementation libs.tests.timberJunitRule // "The one who serves a great Espresso" diff --git a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt index 506ac9c7d0..00828acbb8 100644 --- a/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/crypto/quads/SharedSecureStorageViewModelTest.kt @@ -22,6 +22,7 @@ import im.vector.app.test.InstantRxRule import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeStringProvider import im.vector.app.test.test +import kotlinx.coroutines.test.runBlockingTest import org.junit.Rule import org.junit.Test import org.matrix.android.sdk.api.session.securestorage.IntegrityResult @@ -41,6 +42,7 @@ class SharedSecureStorageViewModelTest { @get:Rule val instantRx = InstantRxRule() + @get:Rule val mvrxTestRule = MvRxTestRule() @@ -50,78 +52,100 @@ class SharedSecureStorageViewModelTest { @Test fun `given a key info with passphrase when initialising then step is EnterPassphrase`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - - val viewModel = createViewModel() - - viewModel.test().assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterPassphrase - )) + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + viewModel + .test(this) + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterPassphrase + )) + .finish() + } } @Test fun `given a key info without passphrase when initialising then step is EnterKey`() { - givenKey(KEY_INFO_WITHOUT_PASSPHRASE) + runBlockingTest { + givenKey(KEY_INFO_WITHOUT_PASSPHRASE) - val viewModel = createViewModel() + val viewModel = createViewModel() - viewModel.test().assertState(aViewState( - hasPassphrase = false, - step = SharedSecureStorageViewState.Step.EnterKey - )) + viewModel + .test(this) + .assertState(aViewState( + hasPassphrase = false, + step = SharedSecureStorageViewState.Step.EnterKey + )) + .finish() + } } @Test fun `given on EnterKey step when going back then dismisses`() { - givenKey(KEY_INFO_WITHOUT_PASSPHRASE) + runBlockingTest { + givenKey(KEY_INFO_WITHOUT_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() - - viewModel.handle(SharedSecureStorageAction.Back) - - test.assertEvents(SharedSecureStorageViewEvent.Dismiss) + val viewModel = createViewModel() + val test = viewModel.test(this) + viewModel.handle(SharedSecureStorageAction.Back) + test + .assertEvents(SharedSecureStorageViewEvent.Dismiss) + .finish() + } } @Test fun `given on passphrase step when using key then step is EnterKey`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.UseKey) + viewModel.handle(SharedSecureStorageAction.UseKey) - test.assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterKey - )) + test + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterKey + )) + .finish() + } } @Test fun `given a key info with passphrase and on EnterKey step when going back then step is EnterPassphrase`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.UseKey) - viewModel.handle(SharedSecureStorageAction.Back) + viewModel.handle(SharedSecureStorageAction.UseKey) + viewModel.handle(SharedSecureStorageAction.Back) - test.assertState(aViewState( - hasPassphrase = true, - step = SharedSecureStorageViewState.Step.EnterPassphrase - )) + test + .assertState(aViewState( + hasPassphrase = true, + step = SharedSecureStorageViewState.Step.EnterPassphrase + )) + .finish() + } } @Test fun `given on passphrase step when going back then dismisses`() { - givenKey(KEY_INFO_WITH_PASSPHRASE) - val viewModel = createViewModel() - val test = viewModel.test() + runBlockingTest { + givenKey(KEY_INFO_WITH_PASSPHRASE) + val viewModel = createViewModel() + val test = viewModel.test(this) - viewModel.handle(SharedSecureStorageAction.Back) + viewModel.handle(SharedSecureStorageAction.Back) - test.assertEvents(SharedSecureStorageViewEvent.Dismiss) + test + .assertEvents(SharedSecureStorageViewEvent.Dismiss) + .finish() + } } private fun createViewModel(): SharedSecureStorageViewModel { diff --git a/vector/src/test/java/im/vector/app/test/Extensions.kt b/vector/src/test/java/im/vector/app/test/Extensions.kt index 5c0cfd265b..67b5090785 100644 --- a/vector/src/test/java/im/vector/app/test/Extensions.kt +++ b/vector/src/test/java/im/vector/app/test/Extensions.kt @@ -20,27 +20,33 @@ import com.airbnb.mvrx.MavericksState import im.vector.app.core.platform.VectorViewEvents import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModelAction -import io.reactivex.observers.TestObserver +import kotlinx.coroutines.CoroutineScope import org.amshove.kluent.shouldBeEqualTo fun String.trimIndentOneLine() = trimIndent().replace("\n", "") -fun VectorViewModel.test(): ViewModelTest { +fun VectorViewModel.test(coroutineScope: CoroutineScope): ViewModelTest { val state = { com.airbnb.mvrx.withState(this) { it } } - //val viewEvents = viewEvents.stream().test() - return ViewModelTest(state) + val viewEvents = viewEvents.stream().test(coroutineScope) + return ViewModelTest(state, viewEvents) } class ViewModelTest( val state: () -> S, - //val viewEvents: TestObserver + val viewEvents: FlowTestObserver ) { - fun assertEvents(vararg expected: VE) { - //viewEvents.assertValues(*expected) + fun assertEvents(vararg expected: VE): ViewModelTest { + viewEvents.assertValues(*expected) + return this } - fun assertState(expected: S) { + fun assertState(expected: S): ViewModelTest { state() shouldBeEqualTo expected + return this + } + + fun finish(){ + viewEvents.finish() } } diff --git a/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt b/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt new file mode 100644 index 0000000000..955922d0c4 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/FlowTestObserver.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import org.junit.Assert.assertEquals + +fun Flow.test(scope: CoroutineScope): FlowTestObserver { + return FlowTestObserver(scope, this) +} + +class FlowTestObserver( + scope: CoroutineScope, + flow: Flow +) { + private val values = mutableListOf() + private val job: Job = flow + .onEach { + values.add(it) + }.launchIn(scope) + + fun assertNoValues(): FlowTestObserver { + assertEquals(emptyList(), this.values) + return this + } + + fun assertValues(vararg values: T): FlowTestObserver { + assertEquals(values.toList(), this.values) + return this + } + + fun finish() { + job.cancel() + } +}