我对交互的单元测试所做的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
是否按预期响应通知。
class UnitTests: XCTestCase {
// MARK: Properties
var storage: UserSettingsStorage!
// MARK: Lifecycle
override func setUp() {
super.setUp()
storage = UserSettingsStorage(cloudKeyValueStore: MockCloudKeyValueStore())
}
override func tearDown() {
storage = nil
super.tearDown()
}
// MARK: Selected List Name
func test_UserSettingsStorage_PublishesSelectedListName_WhenListNameIsSet() {
let listName = "list name"
let publishedExpectation = expectation(forNotification: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: nil) { notification in
return self.storage.selectedListName == listName
}
storage.setSelectedList(name: listName)
wait(for: [publishedExpectation], timeout: 2)
}
}