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.
This commit is contained in:
Nik Clayton 2024-01-17 16:41:41 +01:00 committed by GitHub
parent 993b74691a
commit aaf8cf57f3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 56 additions and 105 deletions

View File

@ -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<Pair<String, Boolean>>()
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

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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 <http://www.gnu.org/licenses>.
*/
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()
}

View File

@ -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