Injectable UserDefaults to fix race condition in tests (#741)

This commit is contained in:
Islam
2025-10-02 12:59:48 +02:00
committed by GitHub
parent 90273cee2d
commit bd11940151
2 changed files with 18 additions and 14 deletions
+8 -5
View File
@@ -20,8 +20,11 @@ final class GeohashBookmarksStore: ObservableObject {
private let geocoder = CLGeocoder() private let geocoder = CLGeocoder()
private var resolving: Set<String> = [] private var resolving: Set<String> = []
#endif #endif
private let storage: UserDefaults
private init() { init(storage: UserDefaults = .standard) {
self.storage = storage
load() load()
} }
@@ -64,7 +67,7 @@ final class GeohashBookmarksStore: ObservableObject {
// MARK: - Persistence // MARK: - Persistence
private func load() { private func load() {
guard let data = UserDefaults.standard.data(forKey: storeKey) else { return } guard let data = storage.data(forKey: storeKey) else { return }
if let arr = try? JSONDecoder().decode([String].self, from: data) { if let arr = try? JSONDecoder().decode([String].self, from: data) {
// Sanitize, normalize, dedupe while preserving order (first occurrence wins) // Sanitize, normalize, dedupe while preserving order (first occurrence wins)
var seen = Set<String>() var seen = Set<String>()
@@ -81,7 +84,7 @@ final class GeohashBookmarksStore: ObservableObject {
membership = seen membership = seen
} }
// Load any saved names // Load any saved names
if let namesData = UserDefaults.standard.data(forKey: namesStoreKey), if let namesData = storage.data(forKey: namesStoreKey),
let dict = try? JSONDecoder().decode([String: String].self, from: namesData) { let dict = try? JSONDecoder().decode([String: String].self, from: namesData) {
bookmarkNames = dict bookmarkNames = dict
} }
@@ -89,13 +92,13 @@ final class GeohashBookmarksStore: ObservableObject {
private func persist() { private func persist() {
if let data = try? JSONEncoder().encode(bookmarks) { if let data = try? JSONEncoder().encode(bookmarks) {
UserDefaults.standard.set(data, forKey: storeKey) storage.set(data, forKey: storeKey)
} }
} }
private func persistNames() { private func persistNames() {
if let data = try? JSONEncoder().encode(bookmarkNames) { if let data = try? JSONEncoder().encode(bookmarkNames) {
UserDefaults.standard.set(data, forKey: namesStoreKey) storage.set(data, forKey: namesStoreKey)
} }
} }
+10 -9
View File
@@ -3,23 +3,25 @@ import XCTest
final class GeohashBookmarksStoreTests: XCTestCase { final class GeohashBookmarksStoreTests: XCTestCase {
let storeKey = "locationChannel.bookmarks" let storeKey = "locationChannel.bookmarks"
var storage: UserDefaults!
var store: GeohashBookmarksStore!
override func setUp() { override func setUp() {
super.setUp() super.setUp()
// Clear persisted state before each test // Unique instance for each test to avoid race condition
UserDefaults.standard.removeObject(forKey: storeKey) storage = UserDefaults(suiteName: UUID().uuidString)
GeohashBookmarksStore.shared._resetForTesting() store = GeohashBookmarksStore(storage: storage!)
} }
override func tearDown() { override func tearDown() {
// Clean after each test storage.removeObject(forKey: storeKey)
UserDefaults.standard.removeObject(forKey: storeKey) store._resetForTesting()
GeohashBookmarksStore.shared._resetForTesting() store = nil
storage = nil
super.tearDown() super.tearDown()
} }
func testToggleAndNormalize() { func testToggleAndNormalize() {
let store = GeohashBookmarksStore.shared
// Start clean // Start clean
XCTAssertTrue(store.bookmarks.isEmpty) XCTAssertTrue(store.bookmarks.isEmpty)
@@ -35,12 +37,11 @@ final class GeohashBookmarksStoreTests: XCTestCase {
} }
func testPersistenceWritten() throws { func testPersistenceWritten() throws {
let store = GeohashBookmarksStore.shared
store.toggle("ezs42") store.toggle("ezs42")
store.toggle("u4pruy") store.toggle("u4pruy")
// Verify persisted JSON contains both (order not enforced here) // Verify persisted JSON contains both (order not enforced here)
guard let data = UserDefaults.standard.data(forKey: storeKey) else { guard let data = storage.data(forKey: storeKey) else {
XCTFail("No persisted data found") XCTFail("No persisted data found")
return return
} }