因此,我正在尝试使用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!
答案 0 :(得分:2)
我要对与config.save()
的交互进行单元测试的目的是创建一个协议,该协议围绕NSUbiquitousKeyValueStore
和NSUbiquitousKeyValueStore
进行自定义的云存储包装。这些实现如下:
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
符合NSUbiquitousKeyValueStore
。 KeyValueStore
不需要执行任何其他操作,因为它已经实现了NSUbiquitousKeyValueStore
中定义的方法。
KeyValueStore
然后,我创建了一个围绕extension NSUbiquitousKeyValueStore: KeyValueStore { }
进行包装的类型。 NSUbiquitousKeyValueStore
接受符合UserSettingsStorage
的任何类型。由于KeyValueStore
和NSUbiquitousKeyValueStore
都符合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