refactor: Replace test preference mocks with InMemorySharedPreferences (#155)
Previously the tests mocked shared preferences with a map and a mock that had to be implemented for each test that needed it. Replace this with `InMemorySharedPreferences`, which provides the normal `SharedPreferences` interface so can be used as a drop-in replacement.
This commit is contained in:
parent
628b5a7db5
commit
0902b0ba49
|
@ -25,6 +25,7 @@ import app.pachli.components.timeline.FiltersRepository
|
|||
import app.pachli.components.timeline.MainCoroutineRule
|
||||
import app.pachli.db.AccountEntity
|
||||
import app.pachli.db.AccountManager
|
||||
import app.pachli.fakes.InMemorySharedPreferences
|
||||
import app.pachli.network.FilterModel
|
||||
import app.pachli.settings.PrefKeys
|
||||
import app.pachli.usecase.TimelineCases
|
||||
|
@ -33,8 +34,6 @@ import okhttp3.ResponseBody.Companion.toResponseBody
|
|||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.kotlin.any
|
||||
import org.mockito.kotlin.doAnswer
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import org.robolectric.Shadows.shadowOf
|
||||
|
@ -44,7 +43,6 @@ import retrofit2.Response
|
|||
@RunWith(AndroidJUnit4::class)
|
||||
abstract class NotificationsViewModelTestBase {
|
||||
protected lateinit var notificationsRepository: NotificationsRepository
|
||||
protected lateinit var sharedPreferencesMap: MutableMap<String, Boolean>
|
||||
protected lateinit var sharedPreferences: SharedPreferences
|
||||
protected lateinit var accountManager: AccountManager
|
||||
protected lateinit var timelineCases: TimelineCases
|
||||
|
@ -71,24 +69,20 @@ abstract class NotificationsViewModelTestBase {
|
|||
|
||||
notificationsRepository = mock()
|
||||
|
||||
// Backing store for sharedPreferences, to allow mutation in tests
|
||||
sharedPreferencesMap = mutableMapOf(
|
||||
PrefKeys.ANIMATE_GIF_AVATARS to false,
|
||||
PrefKeys.ANIMATE_CUSTOM_EMOJIS to false,
|
||||
PrefKeys.ABSOLUTE_TIME_VIEW to false,
|
||||
PrefKeys.SHOW_BOT_OVERLAY to true,
|
||||
PrefKeys.USE_BLURHASH to true,
|
||||
PrefKeys.CONFIRM_REBLOGS to true,
|
||||
PrefKeys.CONFIRM_FAVOURITES to false,
|
||||
PrefKeys.WELLBEING_HIDE_STATS_POSTS to false,
|
||||
PrefKeys.FAB_HIDE to false,
|
||||
sharedPreferences = InMemorySharedPreferences(
|
||||
mapOf(
|
||||
PrefKeys.ANIMATE_GIF_AVATARS to false,
|
||||
PrefKeys.ANIMATE_CUSTOM_EMOJIS to false,
|
||||
PrefKeys.ABSOLUTE_TIME_VIEW to false,
|
||||
PrefKeys.SHOW_BOT_OVERLAY to true,
|
||||
PrefKeys.USE_BLURHASH to true,
|
||||
PrefKeys.CONFIRM_REBLOGS to true,
|
||||
PrefKeys.CONFIRM_FAVOURITES to false,
|
||||
PrefKeys.WELLBEING_HIDE_STATS_POSTS to false,
|
||||
PrefKeys.FAB_HIDE to false,
|
||||
),
|
||||
)
|
||||
|
||||
// Any getBoolean() call looks for the result in sharedPreferencesMap
|
||||
sharedPreferences = mock {
|
||||
on { getBoolean(any(), any()) } doAnswer { sharedPreferencesMap[it.arguments[0]] }
|
||||
}
|
||||
|
||||
accountManager = mock {
|
||||
on { activeAccount } doReturn AccountEntity(
|
||||
id = 1,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package app.pachli.components.notifications
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import app.pachli.appstore.PreferenceChangedEvent
|
||||
import app.pachli.settings.PrefKeys
|
||||
|
@ -65,7 +66,9 @@ class NotificationsViewModelTestStatusDisplayOptions : NotificationsViewModelTes
|
|||
assertThat(defaultStatusDisplayOptions.animateAvatars).isFalse()
|
||||
|
||||
// Given; just a change to one preferences
|
||||
sharedPreferencesMap[PrefKeys.ANIMATE_GIF_AVATARS] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.ANIMATE_GIF_AVATARS, true)
|
||||
}
|
||||
|
||||
// When
|
||||
val updatedOptions = defaultStatusDisplayOptions.make(
|
||||
|
@ -87,7 +90,9 @@ class NotificationsViewModelTestStatusDisplayOptions : NotificationsViewModelTes
|
|||
}
|
||||
|
||||
// Given
|
||||
sharedPreferencesMap[PrefKeys.ANIMATE_GIF_AVATARS] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.ANIMATE_GIF_AVATARS, true)
|
||||
}
|
||||
|
||||
// When
|
||||
eventHub.dispatch(PreferenceChangedEvent(PrefKeys.ANIMATE_GIF_AVATARS))
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package app.pachli.components.notifications
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import app.pachli.appstore.PreferenceChangedEvent
|
||||
import app.pachli.entity.Notification
|
||||
|
@ -53,7 +54,9 @@ class NotificationsViewModelTestUiState : NotificationsViewModelTestBase() {
|
|||
}
|
||||
|
||||
// Given
|
||||
sharedPreferencesMap[PrefKeys.FAB_HIDE] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.FAB_HIDE, true)
|
||||
}
|
||||
|
||||
// When
|
||||
eventHub.dispatch(PreferenceChangedEvent(PrefKeys.FAB_HIDE))
|
||||
|
|
|
@ -26,6 +26,7 @@ import app.pachli.components.timeline.viewmodel.CachedTimelineViewModel
|
|||
import app.pachli.components.timeline.viewmodel.TimelineViewModel
|
||||
import app.pachli.db.AccountEntity
|
||||
import app.pachli.db.AccountManager
|
||||
import app.pachli.fakes.InMemorySharedPreferences
|
||||
import app.pachli.network.FilterModel
|
||||
import app.pachli.settings.AccountPreferenceDataStore
|
||||
import app.pachli.settings.PrefKeys
|
||||
|
@ -50,7 +51,6 @@ import retrofit2.Response
|
|||
@RunWith(AndroidJUnit4::class)
|
||||
abstract class CachedTimelineViewModelTestBase {
|
||||
protected lateinit var cachedTimelineRepository: CachedTimelineRepository
|
||||
protected lateinit var sharedPreferencesMap: MutableMap<String, Boolean>
|
||||
protected lateinit var sharedPreferences: SharedPreferences
|
||||
protected lateinit var accountPreferencesMap: MutableMap<String, Boolean>
|
||||
protected lateinit var accountPreferenceDataStore: AccountPreferenceDataStore
|
||||
|
@ -79,24 +79,20 @@ abstract class CachedTimelineViewModelTestBase {
|
|||
|
||||
cachedTimelineRepository = mock()
|
||||
|
||||
// Backing store for sharedPreferences, to allow mutation in tests
|
||||
sharedPreferencesMap = mutableMapOf(
|
||||
PrefKeys.ANIMATE_GIF_AVATARS to false,
|
||||
PrefKeys.ANIMATE_CUSTOM_EMOJIS to false,
|
||||
PrefKeys.ABSOLUTE_TIME_VIEW to false,
|
||||
PrefKeys.SHOW_BOT_OVERLAY to true,
|
||||
PrefKeys.USE_BLURHASH to true,
|
||||
PrefKeys.CONFIRM_REBLOGS to true,
|
||||
PrefKeys.CONFIRM_FAVOURITES to false,
|
||||
PrefKeys.WELLBEING_HIDE_STATS_POSTS to false,
|
||||
PrefKeys.FAB_HIDE to false,
|
||||
sharedPreferences = InMemorySharedPreferences(
|
||||
mapOf(
|
||||
PrefKeys.ANIMATE_GIF_AVATARS to false,
|
||||
PrefKeys.ANIMATE_CUSTOM_EMOJIS to false,
|
||||
PrefKeys.ABSOLUTE_TIME_VIEW to false,
|
||||
PrefKeys.SHOW_BOT_OVERLAY to true,
|
||||
PrefKeys.USE_BLURHASH to true,
|
||||
PrefKeys.CONFIRM_REBLOGS to true,
|
||||
PrefKeys.CONFIRM_FAVOURITES to false,
|
||||
PrefKeys.WELLBEING_HIDE_STATS_POSTS to false,
|
||||
PrefKeys.FAB_HIDE to false,
|
||||
),
|
||||
)
|
||||
|
||||
// Any getBoolean() call looks for the result in sharedPreferencesMap
|
||||
sharedPreferences = mock {
|
||||
on { getBoolean(any(), any()) } doAnswer { sharedPreferencesMap[it.arguments[0]] }
|
||||
}
|
||||
|
||||
// Backing store for account preferences, to allow mutation in tests
|
||||
accountPreferencesMap = mutableMapOf(
|
||||
PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA to false,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package app.pachli.components.timeline
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import app.pachli.appstore.PreferenceChangedEvent
|
||||
import app.pachli.settings.PrefKeys
|
||||
|
@ -67,7 +68,9 @@ class CachedTimelineViewModelTestStatusDisplayOptions : CachedTimelineViewModelT
|
|||
assertThat(defaultStatusDisplayOptions.animateAvatars).isFalse()
|
||||
|
||||
// Given; just a change to one preferences
|
||||
sharedPreferencesMap[PrefKeys.ANIMATE_GIF_AVATARS] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.ANIMATE_GIF_AVATARS, true)
|
||||
}
|
||||
|
||||
// When
|
||||
val updatedOptions = defaultStatusDisplayOptions.make(
|
||||
|
@ -89,7 +92,9 @@ class CachedTimelineViewModelTestStatusDisplayOptions : CachedTimelineViewModelT
|
|||
}
|
||||
|
||||
// Given
|
||||
sharedPreferencesMap[PrefKeys.ANIMATE_GIF_AVATARS] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.ANIMATE_GIF_AVATARS, true)
|
||||
}
|
||||
|
||||
// When
|
||||
eventHub.dispatch(PreferenceChangedEvent(PrefKeys.ANIMATE_GIF_AVATARS))
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package app.pachli.components.timeline
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import app.pachli.appstore.PreferenceChangedEvent
|
||||
import app.pachli.components.timeline.viewmodel.UiState
|
||||
|
@ -53,7 +54,9 @@ class CachedTimelineViewModelTestUiState : CachedTimelineViewModelTestBase() {
|
|||
}
|
||||
|
||||
// Given
|
||||
sharedPreferencesMap[PrefKeys.FAB_HIDE] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.FAB_HIDE, true)
|
||||
}
|
||||
|
||||
// When
|
||||
eventHub.dispatch(PreferenceChangedEvent(PrefKeys.FAB_HIDE))
|
||||
|
|
|
@ -26,6 +26,7 @@ import app.pachli.components.timeline.viewmodel.NetworkTimelineViewModel
|
|||
import app.pachli.components.timeline.viewmodel.TimelineViewModel
|
||||
import app.pachli.db.AccountEntity
|
||||
import app.pachli.db.AccountManager
|
||||
import app.pachli.fakes.InMemorySharedPreferences
|
||||
import app.pachli.network.FilterModel
|
||||
import app.pachli.settings.AccountPreferenceDataStore
|
||||
import app.pachli.settings.PrefKeys
|
||||
|
@ -49,7 +50,6 @@ import retrofit2.Response
|
|||
@RunWith(AndroidJUnit4::class)
|
||||
abstract class NetworkTimelineViewModelTestBase {
|
||||
protected lateinit var networkTimelineRepository: NetworkTimelineRepository
|
||||
protected lateinit var sharedPreferencesMap: MutableMap<String, Boolean>
|
||||
protected lateinit var sharedPreferences: SharedPreferences
|
||||
protected lateinit var accountPreferencesMap: MutableMap<String, Boolean>
|
||||
protected lateinit var accountPreferenceDataStore: AccountPreferenceDataStore
|
||||
|
@ -78,24 +78,20 @@ abstract class NetworkTimelineViewModelTestBase {
|
|||
|
||||
networkTimelineRepository = mock()
|
||||
|
||||
// Backing store for sharedPreferences, to allow mutation in tests
|
||||
sharedPreferencesMap = mutableMapOf(
|
||||
PrefKeys.ANIMATE_GIF_AVATARS to false,
|
||||
PrefKeys.ANIMATE_CUSTOM_EMOJIS to false,
|
||||
PrefKeys.ABSOLUTE_TIME_VIEW to false,
|
||||
PrefKeys.SHOW_BOT_OVERLAY to true,
|
||||
PrefKeys.USE_BLURHASH to true,
|
||||
PrefKeys.CONFIRM_REBLOGS to true,
|
||||
PrefKeys.CONFIRM_FAVOURITES to false,
|
||||
PrefKeys.WELLBEING_HIDE_STATS_POSTS to false,
|
||||
PrefKeys.FAB_HIDE to false,
|
||||
sharedPreferences = InMemorySharedPreferences(
|
||||
mapOf(
|
||||
PrefKeys.ANIMATE_GIF_AVATARS to false,
|
||||
PrefKeys.ANIMATE_CUSTOM_EMOJIS to false,
|
||||
PrefKeys.ABSOLUTE_TIME_VIEW to false,
|
||||
PrefKeys.SHOW_BOT_OVERLAY to true,
|
||||
PrefKeys.USE_BLURHASH to true,
|
||||
PrefKeys.CONFIRM_REBLOGS to true,
|
||||
PrefKeys.CONFIRM_FAVOURITES to false,
|
||||
PrefKeys.WELLBEING_HIDE_STATS_POSTS to false,
|
||||
PrefKeys.FAB_HIDE to false,
|
||||
),
|
||||
)
|
||||
|
||||
// Any getBoolean() call looks for the result in sharedPreferencesMap
|
||||
sharedPreferences = mock {
|
||||
on { getBoolean(any(), any()) } doAnswer { sharedPreferencesMap[it.arguments[0]] }
|
||||
}
|
||||
|
||||
// Backing store for account preferences, to allow mutation in tests
|
||||
accountPreferencesMap = mutableMapOf(
|
||||
PrefKeys.ALWAYS_SHOW_SENSITIVE_MEDIA to false,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package app.pachli.components.timeline
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import app.pachli.appstore.PreferenceChangedEvent
|
||||
import app.pachli.settings.PrefKeys
|
||||
|
@ -67,7 +68,9 @@ class NetworkTimelineViewModelTestStatusDisplayOptions : NetworkTimelineViewMode
|
|||
assertThat(defaultStatusDisplayOptions.animateAvatars).isFalse()
|
||||
|
||||
// Given; just a change to one preferences
|
||||
sharedPreferencesMap[PrefKeys.ANIMATE_GIF_AVATARS] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.ANIMATE_GIF_AVATARS, true)
|
||||
}
|
||||
|
||||
// When
|
||||
val updatedOptions = defaultStatusDisplayOptions.make(
|
||||
|
@ -89,7 +92,9 @@ class NetworkTimelineViewModelTestStatusDisplayOptions : NetworkTimelineViewMode
|
|||
}
|
||||
|
||||
// Given
|
||||
sharedPreferencesMap[PrefKeys.ANIMATE_GIF_AVATARS] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.ANIMATE_GIF_AVATARS, true)
|
||||
}
|
||||
|
||||
// When
|
||||
eventHub.dispatch(PreferenceChangedEvent(PrefKeys.ANIMATE_GIF_AVATARS))
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package app.pachli.components.timeline
|
||||
|
||||
import androidx.core.content.edit
|
||||
import app.cash.turbine.test
|
||||
import app.pachli.appstore.PreferenceChangedEvent
|
||||
import app.pachli.components.timeline.viewmodel.UiState
|
||||
|
@ -53,7 +54,9 @@ class NetworkTimelineViewModelTestUiState : NetworkTimelineViewModelTestBase() {
|
|||
}
|
||||
|
||||
// Given
|
||||
sharedPreferencesMap[PrefKeys.FAB_HIDE] = true
|
||||
sharedPreferences.edit {
|
||||
putBoolean(PrefKeys.FAB_HIDE, true)
|
||||
}
|
||||
|
||||
// When
|
||||
eventHub.dispatch(PreferenceChangedEvent(PrefKeys.FAB_HIDE))
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* Copyright 2023 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.fakes
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.Editor
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
|
||||
/**
|
||||
* An in-memory implementation of [SharedPreferences] suitable for use in tests.
|
||||
*
|
||||
* @param initialValues optional map of initial values
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class InMemorySharedPreferences(
|
||||
initialValues: Map<String, Any?>? = null,
|
||||
) : SharedPreferences {
|
||||
private var store: MutableMap<String, Any?> = initialValues?.toMutableMap() ?: mutableMapOf()
|
||||
|
||||
private var listeners: MutableSet<OnSharedPreferenceChangeListener> = HashSet()
|
||||
|
||||
private val preferenceEditor: MockSharedPreferenceEditor =
|
||||
MockSharedPreferenceEditor(this, store, listeners)
|
||||
|
||||
override fun getAll(): Map<String, Any?> = store
|
||||
|
||||
override fun getString(key: String?, defValue: String?) = store.getOrDefault(key, defValue) as String?
|
||||
|
||||
override fun getStringSet(key: String?, defValues: MutableSet<String>?) = store.getOrDefault(key, defValues) as MutableSet<String>?
|
||||
|
||||
override fun getInt(key: String, defaultValue: Int) = store.getOrDefault(key, defaultValue) as Int
|
||||
|
||||
override fun getLong(key: String, defaultValue: Long) = store.getOrDefault(key, defaultValue) as Long
|
||||
|
||||
override fun getFloat(key: String, defaultValue: Float) = store.getOrDefault(key, defaultValue) as Float
|
||||
|
||||
override fun getBoolean(key: String, defaultValue: Boolean) = store.getOrDefault(key, defaultValue) as Boolean
|
||||
|
||||
override fun contains(key: String) = key in store
|
||||
|
||||
override fun edit(): Editor = preferenceEditor
|
||||
|
||||
override fun registerOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
override fun unregisterOnSharedPreferenceChangeListener(listener: OnSharedPreferenceChangeListener) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
|
||||
class MockSharedPreferenceEditor(
|
||||
private val sharedPreferences: InMemorySharedPreferences,
|
||||
private val store: MutableMap<String, Any?>,
|
||||
private val listeners: MutableSet<OnSharedPreferenceChangeListener>,
|
||||
) : Editor {
|
||||
private val edits: MutableMap<String, Any?> = mutableMapOf()
|
||||
private var deletes: MutableList<String> = ArrayList()
|
||||
|
||||
override fun putString(key: String, value: String?): Editor {
|
||||
edits[key] = value
|
||||
return this
|
||||
}
|
||||
|
||||
override fun putStringSet(key: String, values: MutableSet<String>?): Editor {
|
||||
edits[key] = values
|
||||
return this
|
||||
}
|
||||
|
||||
override fun putInt(key: String, value: Int): Editor {
|
||||
edits[key] = value
|
||||
return this
|
||||
}
|
||||
|
||||
override fun putLong(key: String, value: Long): Editor {
|
||||
edits[key] = value
|
||||
return this
|
||||
}
|
||||
|
||||
override fun putFloat(key: String, value: Float): Editor {
|
||||
edits[key] = value
|
||||
return this
|
||||
}
|
||||
|
||||
override fun putBoolean(key: String, value: Boolean): Editor {
|
||||
edits[key] = value
|
||||
return this
|
||||
}
|
||||
|
||||
override fun remove(key: String): Editor {
|
||||
edits.remove(key)
|
||||
deletes.add(key)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun clear(): Editor {
|
||||
deletes.clear()
|
||||
store.clear()
|
||||
edits.clear()
|
||||
listeners.forEach {
|
||||
it.onSharedPreferenceChanged(sharedPreferences, null)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
override fun commit(): Boolean {
|
||||
deletes.forEach { key ->
|
||||
store.remove(key)
|
||||
listeners.forEach { it.onSharedPreferenceChanged(sharedPreferences, key) }
|
||||
}
|
||||
|
||||
edits.forEach { entry ->
|
||||
store[entry.key] = entry.value
|
||||
listeners.forEach { it.onSharedPreferenceChanged(sharedPreferences, entry.key) }
|
||||
}
|
||||
|
||||
deletes.clear()
|
||||
edits.clear()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
commit()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue