2

假设我有一个非常常见的属性包装用例,使用UserDefaults.

@propertyWrapper
struct DefaultsStorage<Value> {
    private let key: String
    private let storage: UserDefaults
    
    var wrappedValue: Value? {
        get {
            guard let value = storage.value(forKey: key) as? Value else {
                return nil
            }
            
            return value
        }
        
        nonmutating set {
            storage.setValue(newValue, forKey: key)
        }
    }
    
    init(key: String, storage: UserDefaults = .standard) {
        self.key = key
        self.storage = storage
    }
}

我现在声明一个对象,它将保存我存储在UserDefaults.

struct UserDefaultsStorage {
    @DefaultsStorage(key: "userName")
    var userName: String?
}

现在,当我想在某个地方使用它时,比如说在视图模型中,我会有这样的东西。

final class ViewModel {
    func getUserName() -> String? {
        UserDefaultsStorage().userName
    }
}

这里很少出现问题。

  1. 在这种情况下,我似乎有义务使用.standard用户默认值。如何使用其他/模拟实例测试该视图模型UserDefaults
  2. 如何使用其他/模拟实例测试该属性包装器UserDefaults?我是否必须创建一个新类型,它是上述的干净副本DefaultsStorage,通过模拟UserDefaults并测试该对象?
struct TestUserDefaultsStorage {
    @DefaultsStorage(key: "userName", storage: UserDefaults(suiteName: #file)!)
    var userName: String?
}
4

1 回答 1

2

正如评论中已经提到的@mat,您需要一个protocol模拟UserDefaults依赖项。这样的事情会做:

protocol UserDefaultsStorage {
    func value(forKey key: String) -> Any?
    func setValue(_ value: Any?, forKey key: String)
}

extension UserDefaults: UserDefaultsStorage {}

然后您可以更改您的DefaultsStoragepropertyWrapper 以使用UserDefaultsStorage引用而不是UserDefaults

@propertyWrapper
struct DefaultsStorage<Value> {
    private let key: String
    private let storage: UserDefaultsStorage

    var wrappedValue: Value? {
        get {
            return storage.value(forKey: key) as? Value
        }
        nonmutating set {
            storage.setValue(newValue, forKey: key)
        }
    }

    init(key: String, storage: UserDefaultsStorage = UserDefaults.standard) {
        self.key = key
        self.storage = storage
    }
}

之后,模拟UserDefaultsStorage可能如下所示:

class UserDefaultsStorageMock: UserDefaultsStorage {
    var values: [String: Any]

    init(values: [String: Any] = [:]) {
        self.values = values
    }

    func value(forKey key: String) -> Any? {
        return values[key]
    }

    func setValue(_ value: Any?, forKey key: String) {
        values[key] = value
    }
}

为了测试DefaultsStorage,传递一个实例UserDefaultsStorageMock作为它的存储参数:

import XCTest

class DefaultsStorageTests: XCTestCase {
    class TestUserDefaultsStorage {
        @DefaultsStorage(
            key: "userName",
            storage: UserDefaultsStorageMock(values: ["userName": "TestUsername"])
        )
        var userName: String?
    }
    
    func test_userName() {
        let testUserDefaultsStorage = TestUserDefaultsStorage()
        
        XCTAssertEqual(testUserDefaultsStorage.userName, "TestUsername")
    }
}
于 2020-12-17T18:25:21.263 回答