From aaf8cf57f3d5fb178cdbf4f550ea4fe3292d7d30 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Wed, 17 Jan 2024 16:41:41 +0100 Subject: [PATCH] refactor: Provide TestScope as ApplicationScope in tests (#364) Previously some tests had to manually create dependencies (instead of injecting them) because the dependency required the `TestScope` `CoroutineScope` as one of its dependencies. Resolve this with a `FakeCoroutineScopeModule` that provides `TestScope` as `@ApplicationScope`. The tests can now inject their dependencies, which will use `TestScope`. To inject `AccountPreferenceDataStore` it has been updated to use the current active account when reading or writing preferences. --- .../settings/AccountPreferenceDataStore.kt | 11 +++--- .../CachedTimelineViewModelTestBase.kt | 27 ++----------- .../NetworkTimelineViewModelTestBase.kt | 28 ++------------ .../viewthread/ViewThreadViewModelTest.kt | 27 +------------ .../app/pachli/di/FakeCoroutineScopeModule.kt | 38 +++++++++++++++++++ .../StatusDisplayOptionsRepositoryTest.kt | 30 ++------------- 6 files changed, 56 insertions(+), 105 deletions(-) create mode 100644 app/src/test/java/app/pachli/di/FakeCoroutineScopeModule.kt diff --git a/app/src/main/java/app/pachli/settings/AccountPreferenceDataStore.kt b/app/src/main/java/app/pachli/settings/AccountPreferenceDataStore.kt index 87581c64f..1e860d880 100644 --- a/app/src/main/java/app/pachli/settings/AccountPreferenceDataStore.kt +++ b/app/src/main/java/app/pachli/settings/AccountPreferenceDataStore.kt @@ -3,7 +3,6 @@ package app.pachli.settings import androidx.preference.PreferenceDataStore import app.pachli.core.accounts.AccountManager import app.pachli.core.common.di.ApplicationScope -import app.pachli.core.database.model.AccountEntity import app.pachli.core.preferences.PrefKeys import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -17,18 +16,18 @@ class AccountPreferenceDataStore @Inject constructor( /** Flow of key/values that have been updated in the preferences */ val changes = MutableSharedFlow>() - private val account: AccountEntity = accountManager.activeAccount!! - override fun getBoolean(key: String, defValue: Boolean): Boolean { return when (key) { - PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA -> account.alwaysShowSensitiveMedia - PrefKeys.ALWAYS_OPEN_SPOILER -> account.alwaysOpenSpoiler - PrefKeys.MEDIA_PREVIEW_ENABLED -> account.mediaPreviewEnabled + PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA -> accountManager.activeAccount!!.alwaysShowSensitiveMedia + PrefKeys.ALWAYS_OPEN_SPOILER -> accountManager.activeAccount!!.alwaysOpenSpoiler + PrefKeys.MEDIA_PREVIEW_ENABLED -> accountManager.activeAccount!!.mediaPreviewEnabled else -> defValue } } override fun putBoolean(key: String, value: Boolean) { + val account = accountManager.activeAccount!! + when (key) { PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA -> account.alwaysShowSensitiveMedia = value PrefKeys.ALWAYS_OPEN_SPOILER -> account.alwaysOpenSpoiler = value diff --git a/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestBase.kt b/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestBase.kt index 9c4619a0b..c67cf9f0d 100644 --- a/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestBase.kt +++ b/app/src/test/java/app/pachli/components/timeline/CachedTimelineViewModelTestBase.kt @@ -29,8 +29,6 @@ import app.pachli.core.network.model.TimelineKind import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.preferences.SharedPreferencesRepository import app.pachli.core.testing.rules.MainCoroutineRule -import app.pachli.network.ServerCapabilitiesRepository -import app.pachli.settings.AccountPreferenceDataStore import app.pachli.usecase.TimelineCases import app.pachli.util.StatusDisplayOptionsRepository import at.connyduck.calladapter.networkresult.NetworkResult @@ -41,7 +39,6 @@ import dagger.hilt.android.testing.HiltAndroidTest import java.time.Instant import java.util.Date import javax.inject.Inject -import kotlinx.coroutines.test.TestScope import okhttp3.ResponseBody import okhttp3.ResponseBody.Companion.toResponseBody import org.junit.Before @@ -85,9 +82,10 @@ abstract class CachedTimelineViewModelTestBase { @Inject lateinit var cachedTimelineRepository: CachedTimelineRepository - private lateinit var accountPreferenceDataStore: AccountPreferenceDataStore + @Inject + lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository + protected lateinit var timelineCases: TimelineCases - private lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository protected lateinit var viewModel: TimelineViewModel private val eventHub = EventHub() @@ -127,27 +125,8 @@ abstract class CachedTimelineViewModelTestBase { ), ) - accountPreferenceDataStore = AccountPreferenceDataStore( - accountManager, - TestScope(), - ) - timelineCases = mock() - val serverCapabilitiesRepository = ServerCapabilitiesRepository( - mastodonApi, - accountManager, - TestScope(), - ) - - statusDisplayOptionsRepository = StatusDisplayOptionsRepository( - sharedPreferencesRepository, - serverCapabilitiesRepository, - accountManager, - accountPreferenceDataStore, - TestScope(), - ) - viewModel = CachedTimelineViewModel( SavedStateHandle(mapOf(TimelineViewModel.TIMELINE_KIND_TAG to TimelineKind.Home)), cachedTimelineRepository, diff --git a/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestBase.kt b/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestBase.kt index 30030af72..63ebbb14b 100644 --- a/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestBase.kt +++ b/app/src/test/java/app/pachli/components/timeline/NetworkTimelineViewModelTestBase.kt @@ -28,9 +28,8 @@ import app.pachli.core.network.model.TimelineKind import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.preferences.SharedPreferencesRepository import app.pachli.core.testing.rules.MainCoroutineRule -import app.pachli.network.ServerCapabilitiesRepository -import app.pachli.settings.AccountPreferenceDataStore import app.pachli.usecase.TimelineCases +import app.pachli.util.HiltTestApplication_Application import app.pachli.util.StatusDisplayOptionsRepository import at.connyduck.calladapter.networkresult.NetworkResult import dagger.hilt.android.testing.HiltAndroidRule @@ -38,7 +37,6 @@ import dagger.hilt.android.testing.HiltAndroidTest import java.time.Instant import java.util.Date import javax.inject.Inject -import kotlinx.coroutines.test.TestScope import okhttp3.ResponseBody import okhttp3.ResponseBody.Companion.toResponseBody import org.junit.Before @@ -77,9 +75,10 @@ abstract class NetworkTimelineViewModelTestBase { @Inject lateinit var networkTimelineRepository: NetworkTimelineRepository - private lateinit var accountPreferenceDataStore: AccountPreferenceDataStore + @Inject + lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository + protected lateinit var timelineCases: TimelineCases - private lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository protected lateinit var viewModel: TimelineViewModel private val eventHub = EventHub() @@ -119,27 +118,8 @@ abstract class NetworkTimelineViewModelTestBase { ), ) - accountPreferenceDataStore = AccountPreferenceDataStore( - accountManager, - TestScope(), - ) - timelineCases = mock() - val serverCapabilitiesRepository = ServerCapabilitiesRepository( - mastodonApi, - accountManager, - TestScope(), - ) - - statusDisplayOptionsRepository = StatusDisplayOptionsRepository( - sharedPreferencesRepository, - serverCapabilitiesRepository, - accountManager, - accountPreferenceDataStore, - TestScope(), - ) - viewModel = NetworkTimelineViewModel( SavedStateHandle(mapOf(TimelineViewModel.TIMELINE_KIND_TAG to TimelineKind.Bookmarks)), networkTimelineRepository, diff --git a/app/src/test/java/app/pachli/components/viewthread/ViewThreadViewModelTest.kt b/app/src/test/java/app/pachli/components/viewthread/ViewThreadViewModelTest.kt index 66e20388a..0d3c946b2 100644 --- a/app/src/test/java/app/pachli/components/viewthread/ViewThreadViewModelTest.kt +++ b/app/src/test/java/app/pachli/components/viewthread/ViewThreadViewModelTest.kt @@ -20,8 +20,6 @@ import app.pachli.core.network.model.Account import app.pachli.core.network.model.StatusContext import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.preferences.SharedPreferencesRepository -import app.pachli.network.ServerCapabilitiesRepository -import app.pachli.settings.AccountPreferenceDataStore import app.pachli.usecase.TimelineCases import app.pachli.util.StatusDisplayOptionsRepository import at.connyduck.calladapter.networkresult.NetworkResult @@ -36,7 +34,6 @@ import java.util.Date import javax.inject.Inject import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestScope import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -113,9 +110,8 @@ class ViewThreadViewModelTest { @BindValue @JvmField val filtersRepository: FiltersRepository = mock() - private lateinit var accountPreferenceDataStore: AccountPreferenceDataStore - - private lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository + @Inject + lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository private lateinit var viewModel: ViewThreadViewModel @@ -158,30 +154,11 @@ class ViewThreadViewModelTest { ), ) - accountPreferenceDataStore = AccountPreferenceDataStore( - accountManager, - TestScope(), - ) - val cachedTimelineRepository: CachedTimelineRepository = mock { onBlocking { getStatusViewData(any()) } doReturn emptyMap() onBlocking { getStatusTranslations(any()) } doReturn emptyMap() } - val serverCapabilitiesRepository = ServerCapabilitiesRepository( - mastodonApi, - accountManager, - TestScope(), - ) - - statusDisplayOptionsRepository = StatusDisplayOptionsRepository( - sharedPreferencesRepository, - serverCapabilitiesRepository, - accountManager, - accountPreferenceDataStore, - TestScope(), - ) - viewModel = ViewThreadViewModel( mastodonApi, timelineCases, diff --git a/app/src/test/java/app/pachli/di/FakeCoroutineScopeModule.kt b/app/src/test/java/app/pachli/di/FakeCoroutineScopeModule.kt new file mode 100644 index 000000000..dd3e13a87 --- /dev/null +++ b/app/src/test/java/app/pachli/di/FakeCoroutineScopeModule.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2024 Pachli Association + * + * This file is a part of Pachli. + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Pachli is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Pachli; if not, + * see . + */ + +package app.pachli.di + +import app.pachli.core.common.di.ApplicationScope +import app.pachli.core.common.di.CoroutineScopeModule +import dagger.Module +import dagger.Provides +import dagger.hilt.components.SingletonComponent +import dagger.hilt.testing.TestInstallIn +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.TestScope + +@TestInstallIn( + components = [SingletonComponent::class], + replaces = [CoroutineScopeModule::class], +) +@Module +object FakeCoroutineScopeModule { + @ApplicationScope + @Provides + fun providesApplicationScope(): CoroutineScope = TestScope() +} diff --git a/app/src/test/java/app/pachli/util/StatusDisplayOptionsRepositoryTest.kt b/app/src/test/java/app/pachli/util/StatusDisplayOptionsRepositoryTest.kt index 43b71fb8a..dbfb4050d 100644 --- a/app/src/test/java/app/pachli/util/StatusDisplayOptionsRepositoryTest.kt +++ b/app/src/test/java/app/pachli/util/StatusDisplayOptionsRepositoryTest.kt @@ -21,14 +21,12 @@ import androidx.core.content.edit import androidx.test.ext.junit.runners.AndroidJUnit4 import app.cash.turbine.test import app.pachli.PachliApplication -import app.pachli.components.compose.HiltTestApplication_Application import app.pachli.core.accounts.AccountManager import app.pachli.core.network.model.Account import app.pachli.core.network.retrofit.MastodonApi import app.pachli.core.preferences.PrefKeys import app.pachli.core.preferences.SharedPreferencesRepository import app.pachli.core.testing.rules.MainCoroutineRule -import app.pachli.network.ServerCapabilitiesRepository import app.pachli.settings.AccountPreferenceDataStore import com.google.common.truth.Truth.assertThat import dagger.hilt.android.testing.CustomTestApplication @@ -38,7 +36,6 @@ import java.time.Instant import java.util.Date import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Before @@ -72,11 +69,11 @@ class StatusDisplayOptionsRepositoryTest { @Inject lateinit var sharedPreferencesRepository: SharedPreferencesRepository - // Not injected as it expects an active account, so constructed by hand in setup() - private lateinit var accountPreferenceDataStore: AccountPreferenceDataStore + @Inject + lateinit var accountPreferenceDataStore: AccountPreferenceDataStore - // Not injected, as it depends on accountPreferenceDataStore - private lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository + @Inject + lateinit var statusDisplayOptionsRepository: StatusDisplayOptionsRepository private val defaultStatusDisplayOptions = StatusDisplayOptions() @@ -102,25 +99,6 @@ class StatusDisplayOptionsRepositoryTest { header = "", ), ) - - accountPreferenceDataStore = AccountPreferenceDataStore( - accountManager, - TestScope(), - ) - - val serverCapabilitiesRepository = ServerCapabilitiesRepository( - mastodonApi, - accountManager, - TestScope(), - ) - - statusDisplayOptionsRepository = StatusDisplayOptionsRepository( - sharedPreferencesRepository, - serverCapabilitiesRepository, - accountManager, - accountPreferenceDataStore, - TestScope(), - ) } @Test