如何模拟NSUbiquitousKeyValueStore进行单元测试?

时间:2020-04-30 21:35:42

标签: ios swift xcode unit-testing nsubiquitouskeyvaluestore

因此,我正在尝试使用UserDefaults和NSUbiquitousKeyValueStore进行单元测试的类。我可以使用UserDefaults(suiteName:#file)轻松模拟UserDefaults,例如,但我不知道如何模拟NSUbiquitousKeyValueStore。我似乎在SO上找不到与此相关的任何线程,并且缺少我的google-fu。

这是我的测试课程的开始,仅供参考:

class ReviewTests: XCTestCase {

    private var userDefaults: UserDefaults = UserDefaults(suiteName: #file)!
    private var ubiquitousKeyValueStore: NSUbiquitousKeyValueStore = // How do I mock this?
    private var reviewPromptController: ReviewPromptController!

1 个答案:

答案 0 :(得分:2)

我要对与config.save()的交互进行单元测试的目的是创建一个协议,该协议围绕NSUbiquitousKeyValueStoreNSUbiquitousKeyValueStore进行自定义的云存储包装。这些实现如下:

NSUbiquitousKeyValueStore

protocol KeyValueStore: class { // MARK: Properties static var didChangeExternallyNotification: Notification.Name { get } // MARK: Reading func object(forKey aKey: String) -> Any? func string(forKey aKey: String) -> String? func array(forKey aKey: String) -> [Any]? func dictionary(forKey aKey: String) -> [String : Any]? func data(forKey aKey: String) -> Data? func longLong(forKey aKey: String) -> Int64 func double(forKey aKey: String) -> Double func bool(forKey aKey: String) -> Bool // MARK: Writing func set(_ anObject: Any?, forKey aKey: String) func set(_ aString: String?, forKey aKey: String) func set(_ aData: Data?, forKey aKey: String) func set(_ anArray: [Any]?, forKey aKey: String) func set(_ aDictionary: [String : Any]?, forKey aKey: String) func set(_ value: Int64, forKey aKey: String) func set(_ value: Double, forKey aKey: String) func set(_ value: Bool, forKey aKey: String) func removeObject(forKey aKey: String) } 是一个协议,其中包含KeyValueStore具有的方法签名。这样,我可以创建一个可以在可以使用NSUbiquitousKeyValueStore实例的测试中使用的类型。

接下来,我使NSUbiquitousKeyValueStore符合NSUbiquitousKeyValueStoreKeyValueStore不需要执行任何其他操作,因为它已经实现了NSUbiquitousKeyValueStore中定义的方法。

KeyValueStore 

然后,我创建了一个围绕extension NSUbiquitousKeyValueStore: KeyValueStore { } 进行包装的类型。 NSUbiquitousKeyValueStore接受符合UserSettingsStorage的任何类型。由于KeyValueStoreNSUbiquitousKeyValueStore都符合MockCloudKeyValueStore,因此我可以在生产中传递KeyValueStore的实例,在测试中传递NSUbiquitousKeyValueStore的实例。

MockCloudKeyValueStore
class MockCloudKeyValueStore: KeyValueStore {

    // MARK: Properties

    // KeyValueStore
    static var didChangeExternallyNotification: Notification.Name = Notification.Name(NSUbiquitousKeyValueStore.didChangeExternallyNotification.rawValue)

    // Helpers
    private var arrays: [String: [Any]] = [:]
    private var bools: [String: Bool] = [:]
    private var datas: [String: Data] = [:]
    private var dictionaries: [String: [String: Any]] = [:]
    private var doubles: [String: Double] = [:]
    private var longLongs: [String: Int64] = [:]
    private var objects: [String: Any] = [:]
    private var strings: [String: String] = [:]

    // MARK: Reading
    func object(forKey aKey: String) -> Any? {
        objects[aKey]
    }

    func string(forKey aKey: String) -> String? {
        strings[aKey]
    }

    func array(forKey aKey: String) -> [Any]? {
        arrays[aKey]
    }

    func dictionary(forKey aKey: String) -> [String : Any]? {
        dictionaries[aKey]
    }

    func data(forKey aKey: String) -> Data? {
        datas[aKey]
    }

    func longLong(forKey aKey: String) -> Int64 {
        longLongs[aKey] ?? 0
    }

    func double(forKey aKey: String) -> Double {
        doubles[aKey] ?? 0
    }

    func bool(forKey aKey: String) -> Bool {
        bools[aKey] ?? false
    }

    // MARK: Writing

    func set(_ anObject: Any?, forKey aKey: String) {
        objects[aKey] = anObject
        postServerDidChangeNotification()
    }

    func set(_ aString: String?, forKey aKey: String) {
        strings[aKey] = aString
        postServerDidChangeNotification()
    }

    func set(_ aData: Data?, forKey aKey: String) {
        datas[aKey] = aData
        postServerDidChangeNotification()
    }

    func set(_ anArray: [Any]?, forKey aKey: String) {
        arrays[aKey] = anArray
        postServerDidChangeNotification()
    }

    func set(_ aDictionary: [String : Any]?, forKey aKey: String) {
        dictionaries[aKey] = aDictionary
        postServerDidChangeNotification()
    }

    func set(_ value: Int64, forKey aKey: String) {
        longLongs[aKey] = value
        postServerDidChangeNotification()
    }

    func set(_ value: Double, forKey aKey: String) {
        doubles[aKey] = value
        postServerDidChangeNotification()
    }

    func set(_ value: Bool, forKey aKey: String) {
        bools[aKey] = value
        postServerDidChangeNotification()
    }

    func removeObject(forKey aKey: String) {
        arrays[aKey] = nil
        bools[aKey] = nil
        datas[aKey] = nil
        dictionaries[aKey] = nil
        doubles[aKey] = nil
        longLongs[aKey] = nil
        objects[aKey] = nil
        strings[aKey] = nil
        postServerDidChangeNotification()
    }

    // MARK: Helpers

    private func postServerDidChangeNotification() {
        var userInfo = [AnyHashable : Any]()
        let changeReason = NSUbiquitousKeyValueStoreServerChange
        userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] = changeReason
        NotificationCenter.default.post(name: Self.didChangeExternallyNotification, object: nil, userInfo: userInfo)
    }

}

最后,我可以在单元测试中使用我的模拟,确保从模拟中调用class UserSettingsStorage: ObservableObject { // MARK: Properties private let cloudKeyValueStore: KeyValueStore private let notificationCenter: NotificationCenter @Published var selectedListName = "" // MARK: Initialization init(cloudKeyValueStore: KeyValueStore, notificationCenter: NotificationCenter = .default) { self.cloudKeyValueStore = cloudKeyValueStore self.notificationCenter = notificationCenter observeUbiquitousKeyValueStoreDidChangeExternallyNotification() } // MARK: Reading private func getSelectedListIndex() { selectedListName = cloudKeyValueStore.string(forKey: "selectedListIndexKey") ?? "" } // MARK: Writing // Cloud Key-Value Store func setSelectedList(name: String) { selectedListName = name cloudKeyValueStore.set(name, forKey: "selectedListIndexKey") } // MARK: Notification Observation private func observeUbiquitousKeyValueStoreDidChangeExternallyNotification(in center: NotificationCenter = .default) { center.addObserver(self, selector: #selector(handleUbiquitousKeyValueStoreDidChangeExternallyNotification(notification:)), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: nil) } // MARK: Notification Selectors @objc private func handleUbiquitousKeyValueStoreDidChangeExternallyNotification(notification: Notification) { // Check for the reasons listed at https://developer.apple.com/documentation/foundation/nsubiquitouskeyvaluestore/1433687-change_reason_values to handle errors/blockers if let userInfo = notification.userInfo, let changeReason = userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] as? NSNumber { switch changeReason.intValue { case NSUbiquitousKeyValueStoreServerChange: getSelectedListIndex() case NSUbiquitousKeyValueStoreInitialSyncChange: print("initial sync change") case NSUbiquitousKeyValueStoreQuotaViolationChange: print("quota violation change") case NSUbiquitousKeyValueStoreAccountChange: print("account change") default: print("unknown change") } } else { print("userInfo not available for Notification", notification) } } } ,然后验证我在NSUbiquiousKeyValueStore.didChangeExternallyNotification周围的包装是否按预期响应了通知。

NSUbiquitousKeyValueStore