From f14311afda89dccd80ee74bac5e665808b78cd63 Mon Sep 17 00:00:00 2001 From: Matthias Urhahn <matthias.urhahn@sap.com> Date: Mon, 3 May 2021 15:59:11 +0200 Subject: [PATCH] Fix flaky unit test. (#3027) Co-authored-by: harambasicluka <64483219+harambasicluka@users.noreply.github.com> Co-authored-by: Juraj Kusnier <jurajkusnier@users.noreply.github.com> --- .reuse/dep5 | 4 ++ .../debugoptions/ui/DebugOptionsFragment.kt | 2 +- .../ui/DebugOptionsFragmentViewModel.kt | 18 +++--- .../coronawarnapp/util/ui/SmartLiveData.kt | 57 ------------------- ...taTestExtension.kt => LiveDataTestUtil.kt} | 18 +++++- .../ui/DebugOptionsFragmentViewModelTest.kt | 43 +++----------- 6 files changed, 38 insertions(+), 104 deletions(-) delete mode 100644 Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt rename Corona-Warn-App/src/test/java/testhelpers/extensions/{LiveDataTestExtension.kt => LiveDataTestUtil.kt} (73%) diff --git a/.reuse/dep5 b/.reuse/dep5 index ae4ac319f..25db19164 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -54,4 +54,8 @@ License: Apache-2.0 Files: Corona-Warn-App/src/main/res/font/roboto.ttf Copyright: 2011 Google Inc. +License: Apache-2.0 + +Files: Corona-Warn-App/src/test/java/testhelpers/extensions/LiveDataTestUtil.kt +Copyright: 2019 The Android Open Source Project License: Apache-2.0 \ No newline at end of file diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt index 638755b2c..b0e02bfce 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragment.kt @@ -75,7 +75,7 @@ class DebugOptionsFragment : Fragment(R.layout.fragment_test_debugoptions), Auto environmentPubkeyAppconfig.text = "AppConfigPubKey:\n${state.pubKeyAppConfig}" } } - vm.environmentChangeEvent.observe2(this) { + vm.environmentStateChange.observe2(this) { showSnackBar("Environment changed to: $it\nForce stop & restart the app!") } } diff --git a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModel.kt b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModel.kt index 9d959a1db..f7f5f5d48 100644 --- a/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModel.kt +++ b/Corona-Warn-App/src/deviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModel.kt @@ -1,5 +1,6 @@ package de.rki.coronawarnapp.test.debugoptions.ui +import androidx.lifecycle.asLiveData import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import de.rki.coronawarnapp.environment.EnvironmentSetup @@ -7,25 +8,24 @@ import de.rki.coronawarnapp.environment.EnvironmentSetup.Type.Companion.toEnviro import de.rki.coronawarnapp.test.debugoptions.ui.EnvironmentState.Companion.toEnvironmentState import de.rki.coronawarnapp.util.coroutine.DispatcherProvider import de.rki.coronawarnapp.util.ui.SingleLiveEvent -import de.rki.coronawarnapp.util.ui.smartLiveData import de.rki.coronawarnapp.util.viewmodel.CWAViewModel import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory +import kotlinx.coroutines.flow.MutableStateFlow class DebugOptionsFragmentViewModel @AssistedInject constructor( private val envSetup: EnvironmentSetup, dispatcherProvider: DispatcherProvider ) : CWAViewModel(dispatcherProvider = dispatcherProvider) { - val environmentState by smartLiveData { - envSetup.toEnvironmentState() - } - val environmentChangeEvent = SingleLiveEvent<EnvironmentSetup.Type>() + private val environmentStateFlow = MutableStateFlow(envSetup.toEnvironmentState()) + val environmentState = environmentStateFlow.asLiveData(context = dispatcherProvider.Default) + val environmentStateChange = SingleLiveEvent<EnvironmentState>() fun selectEnvironmentTytpe(type: String) { - environmentState.update { - envSetup.currentEnvironment = type.toEnvironmentType() - environmentChangeEvent.postValue(envSetup.currentEnvironment) - envSetup.toEnvironmentState() + envSetup.currentEnvironment = type.toEnvironmentType() + envSetup.toEnvironmentState().let { + environmentStateFlow.value = it + environmentStateChange.postValue(it) } } diff --git a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt b/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt deleted file mode 100644 index d598cdf5b..000000000 --- a/Corona-Warn-App/src/main/java/de/rki/coronawarnapp/util/ui/SmartLiveData.kt +++ /dev/null @@ -1,57 +0,0 @@ -package de.rki.coronawarnapp.util.ui - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -fun <T : Any> ViewModel.smartLiveData( - dispatcher: CoroutineDispatcher = Dispatchers.Default, - liveDataFactory: (ViewModel, CoroutineDispatcher) -> SmartLiveData<T> = { vm, disp -> - SmartLiveData(vm, disp) - }, - initAction: suspend () -> T -) = SmartLiveDataProperty(dispatcher, initAction, liveDataFactory) - -class SmartLiveDataProperty<T : Any, LV : SmartLiveData<T>>( - private val dispatcher: CoroutineDispatcher = Dispatchers.Default, - private val initialValueProvider: suspend () -> T, - private val liveDataFactory: (ViewModel, CoroutineDispatcher) -> LV -) : ReadOnlyProperty<ViewModel, SmartLiveData<T>> { - - private var liveData: SmartLiveData<T>? = null - - override fun getValue( - thisRef: ViewModel, - property: KProperty<*> - ): SmartLiveData<T> { - liveData?.let { - return@getValue it - } - - return liveDataFactory(thisRef, dispatcher).also { - liveData = it - thisRef.viewModelScope.launch(context = dispatcher) { - it.postValue(initialValueProvider()) - } - } - } -} - -open class SmartLiveData<T : Any>( - private val viewModel: ViewModel, - private val dispatcher: CoroutineDispatcher -) : MutableLiveData<T>() { - - fun update(updateAction: (T) -> T) { - observeOnce { - viewModel.viewModelScope.launch(context = dispatcher) { - postValue(updateAction(it)) - } - } - } -} diff --git a/Corona-Warn-App/src/test/java/testhelpers/extensions/LiveDataTestExtension.kt b/Corona-Warn-App/src/test/java/testhelpers/extensions/LiveDataTestUtil.kt similarity index 73% rename from Corona-Warn-App/src/test/java/testhelpers/extensions/LiveDataTestExtension.kt rename to Corona-Warn-App/src/test/java/testhelpers/extensions/LiveDataTestUtil.kt index 1041e72dd..80af61128 100644 --- a/Corona-Warn-App/src/test/java/testhelpers/extensions/LiveDataTestExtension.kt +++ b/Corona-Warn-App/src/test/java/testhelpers/extensions/LiveDataTestUtil.kt @@ -1,9 +1,25 @@ -package testhelpers.extensions +/* + * Copyright (C) 2019 The Android Open Source Project + * + * 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. + */ /** * Thanks to https://github.com/android/architecture-components-samples/blob/master/LiveDataSample/app/src/test/java/com/android/example/livedatabuilder/util/LiveDataTestUtil.kt */ +package testhelpers.extensions + import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import java.util.concurrent.CountDownLatch diff --git a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt index bc5ae9e45..c1b802516 100644 --- a/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt +++ b/Corona-Warn-App/src/testDeviceForTesters/java/de/rki/coronawarnapp/test/debugoptions/ui/DebugOptionsFragmentViewModelTest.kt @@ -1,25 +1,19 @@ package de.rki.coronawarnapp.test.debugoptions.ui -import androidx.lifecycle.Observer import de.rki.coronawarnapp.environment.EnvironmentSetup import io.kotest.matchers.shouldBe import io.mockk.MockKAnnotations -import io.mockk.Runs import io.mockk.every import io.mockk.impl.annotations.MockK -import io.mockk.just -import io.mockk.mockk -import io.mockk.verify import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import testhelpers.BaseTestInstrumentation import testhelpers.TestDispatcherProvider -import testhelpers.extensions.CoroutinesTestExtension import testhelpers.extensions.InstantExecutorExtension -import testhelpers.flakyTest +import testhelpers.extensions.getOrAwaitValue -@ExtendWith(InstantExecutorExtension::class, CoroutinesTestExtension::class) +@ExtendWith(InstantExecutorExtension::class) class DebugOptionsFragmentViewModelTest : BaseTestInstrumentation() { @MockK private lateinit var environmentSetup: EnvironmentSetup @@ -54,38 +48,15 @@ class DebugOptionsFragmentViewModelTest : BaseTestInstrumentation() { ) @Test - fun `toggeling the env works`() = flakyTest { + fun `toggeling the env works`() { currentEnvironment = EnvironmentSetup.Type.DEV val vm = createViewModel() - - val states = mutableListOf<EnvironmentState>() - val observerState = mockk<Observer<EnvironmentState>>() - every { observerState.onChanged(capture(states)) } just Runs - vm.environmentState.observeForever(observerState) - - val events = mutableListOf<EnvironmentSetup.Type>() - val observerEvent = mockk<Observer<EnvironmentSetup.Type>>() - every { observerEvent.onChanged(capture(events)) } just Runs - vm.environmentChangeEvent.observeForever(observerEvent) + vm.environmentState.getOrAwaitValue().current shouldBe EnvironmentSetup.Type.DEV vm.selectEnvironmentTytpe(EnvironmentSetup.Type.DEV.rawKey) - vm.selectEnvironmentTytpe(EnvironmentSetup.Type.WRU_XA.rawKey) - - verify(exactly = 3, timeout = 3000) { observerState.onChanged(any()) } - verify(exactly = 2, timeout = 3000) { observerEvent.onChanged(any()) } - - states[0].apply { - current shouldBe EnvironmentSetup.Type.DEV - } - - states[1].apply { - current shouldBe EnvironmentSetup.Type.DEV - } - events[0] shouldBe EnvironmentSetup.Type.DEV + vm.environmentState.getOrAwaitValue().current shouldBe EnvironmentSetup.Type.DEV - states[2].apply { - current shouldBe EnvironmentSetup.Type.WRU_XA - } - events[1] shouldBe EnvironmentSetup.Type.WRU_XA + vm.selectEnvironmentTytpe(EnvironmentSetup.Type.WRU_XA.rawKey) + vm.environmentState.getOrAwaitValue().current shouldBe EnvironmentSetup.Type.WRU_XA } } -- GitLab