模拟 UNNotificationResponse 和 UNNotification(以及其他 iOS 平台 类,其中 init() 标记为不可用)
Mock UNNotificationResponse & UNNotification (and other iOS platform classes with init() marked as unavailable)
我需要模拟 UNNotificationResponse
和 UNNotification
以便我可以测试我的实现:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)
但是我不能有效地将这些 类 子类化,因为 init()
被特别标记为 unavailable
,如果我尝试这样会导致编译错误:
/Path/to/PushClientTests.swift:38:5: Cannot override 'init' which has been marked unavailable
这里可以采用哪些替代方法?我研究了面向协议的编程路线,但是由于我不控制被调用的 API,我无法修改它以采用我编写的协议。
简短回答:你不能!
相反,分解您对
的实施
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)
并测试您从那里调用的方法。
测试愉快:)
您似乎可以初始化 UNNotificationContent
个对象。我选择重新设计我的推送处理方法以获取 UNNotificationContent
个对象而不是 UNNotificationResponse
/UNNotification
.
要做到这一点,您需要执行以下操作。
在调试时获取对象的真实示例,并使用您的模拟器保存在文件系统中。
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
let encodedObject = NSKeyedArchiver.archivedData(withRootObject: notification)
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/notification.mock"
fileManager.createFile(atPath: path, contents: encodedObject, attributes: nil)
在您的 Mac 中找到对象并将文件添加到与测试相同的目标中 class。
现在在您的测试中取消存档。
let path = Bundle(for: type(of: self)).path(forResource: "notification", ofType: "mock")
let data = FileManager.default.contents(atPath: path ?? "")
let notification = NSKeyedUnarchiver.unarchiveObject(with: data ?? Data()) as? UNNotification
我使用下一个扩展来创建 UNNotificationResponse 和 UNNotification 实例,同时在 iOS 上实现推送通知的单元测试:
extension UNNotificationResponse {
static func testNotificationResponse(with payloadFilename: String) -> UNNotificationResponse {
let parameters = parametersFromFile(payloadFilename) // 1
let request = notificationRequest(with: parameters) // 2
return UNNotificationResponse(coder: TestNotificationCoder(with: request))! // 3
}
}
- 从文件加载推送通知负载
- 使用 userInfo 中的指定参数创建 UNNotificationRequest 实例
- 使用 NSCoder 子类创建 UNNotificationResponse 实例
以下是我在上面使用的函数:
extension UNNotificationResponse {
private static func notificationRequest(with parameters: [AnyHashable: Any]) -> UNNotificationRequest {
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Test Title"
notificationContent.body = "Test Body"
notificationContent.userInfo = parameters
let dateInfo = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
let trigger = UNCalendarNotificationTrigger(dateMatching: dateInfo, repeats: false)
let notificationRequest = UNNotificationRequest(identifier: "testIdentifier", content: notificationContent, trigger: trigger)
return notificationRequest
}
}
fileprivate class TestNotificationCoder: NSCoder {
private enum FieldKey: String {
case date, request, sourceIdentifier, intentIdentifiers, notification, actionIdentifier, originIdentifier, targetConnectionEndpoint, targetSceneIdentifier
}
private let testIdentifier = "testIdentifier"
private let request: UNNotificationRequest
override var allowsKeyedCoding: Bool { true }
init(with request: UNNotificationRequest) {
self.request = request
}
override func decodeObject(forKey key: String) -> Any? {
let fieldKey = FieldKey(rawValue: key)
switch fieldKey {
case .date:
return Date()
case .request:
return request
case .sourceIdentifier, .actionIdentifier, .originIdentifier:
return testIdentifier
case .notification:
return UNNotification(coder: self)
default:
return nil
}
}
}
如果 UNNotificationResponse
值无关紧要,而您只想执行应用委托的那个方法,您可以通过子类化 NSKeyedArchiver
创建模拟来实现此目的,如下所示:
class MockCoder: NSKeyedArchiver {
override func decodeObject(forKey key: String) -> Any { "" }
}
然后你可以这样称呼它:
let notificationMock = try XCTUnwrap(UNNotificationResponse(coder: MockCoder()))
appDelegate.userNotificationCenter(UNUserNotificationCenter.current(), didReceive: notificationMock) { }
您的应用委托的 userNotificationCenter(_:didReceive:withCompletionHandler:)
方法现在将被调用,允许您随心所欲地断言(至少假设没有针对通知本身的断言)。
您可以使用 class_createInstance(UNNotification.classForKeyedArchiver() ...)
并将值转换为 UNNotification
如果您想操纵其 content
和 date
成员,您可以子 class UNNotification
并使用相同的公式«更改 class 名称和投射到你的子class»来创建它,然后你覆盖那些开放的成员和return任何你想要的
我需要模拟 UNNotificationResponse
和 UNNotification
以便我可以测试我的实现:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)
但是我不能有效地将这些 类 子类化,因为 init()
被特别标记为 unavailable
,如果我尝试这样会导致编译错误:
/Path/to/PushClientTests.swift:38:5: Cannot override 'init' which has been marked unavailable
这里可以采用哪些替代方法?我研究了面向协议的编程路线,但是由于我不控制被调用的 API,我无法修改它以采用我编写的协议。
简短回答:你不能!
相反,分解您对
的实施func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)
并测试您从那里调用的方法。
测试愉快:)
您似乎可以初始化 UNNotificationContent
个对象。我选择重新设计我的推送处理方法以获取 UNNotificationContent
个对象而不是 UNNotificationResponse
/UNNotification
.
要做到这一点,您需要执行以下操作。
在调试时获取对象的真实示例,并使用您的模拟器保存在文件系统中。
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
let encodedObject = NSKeyedArchiver.archivedData(withRootObject: notification)
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/notification.mock"
fileManager.createFile(atPath: path, contents: encodedObject, attributes: nil)
在您的 Mac 中找到对象并将文件添加到与测试相同的目标中 class。
现在在您的测试中取消存档。
let path = Bundle(for: type(of: self)).path(forResource: "notification", ofType: "mock")
let data = FileManager.default.contents(atPath: path ?? "")
let notification = NSKeyedUnarchiver.unarchiveObject(with: data ?? Data()) as? UNNotification
我使用下一个扩展来创建 UNNotificationResponse 和 UNNotification 实例,同时在 iOS 上实现推送通知的单元测试:
extension UNNotificationResponse {
static func testNotificationResponse(with payloadFilename: String) -> UNNotificationResponse {
let parameters = parametersFromFile(payloadFilename) // 1
let request = notificationRequest(with: parameters) // 2
return UNNotificationResponse(coder: TestNotificationCoder(with: request))! // 3
}
}
- 从文件加载推送通知负载
- 使用 userInfo 中的指定参数创建 UNNotificationRequest 实例
- 使用 NSCoder 子类创建 UNNotificationResponse 实例
以下是我在上面使用的函数:
extension UNNotificationResponse {
private static func notificationRequest(with parameters: [AnyHashable: Any]) -> UNNotificationRequest {
let notificationContent = UNMutableNotificationContent()
notificationContent.title = "Test Title"
notificationContent.body = "Test Body"
notificationContent.userInfo = parameters
let dateInfo = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: Date())
let trigger = UNCalendarNotificationTrigger(dateMatching: dateInfo, repeats: false)
let notificationRequest = UNNotificationRequest(identifier: "testIdentifier", content: notificationContent, trigger: trigger)
return notificationRequest
}
}
fileprivate class TestNotificationCoder: NSCoder {
private enum FieldKey: String {
case date, request, sourceIdentifier, intentIdentifiers, notification, actionIdentifier, originIdentifier, targetConnectionEndpoint, targetSceneIdentifier
}
private let testIdentifier = "testIdentifier"
private let request: UNNotificationRequest
override var allowsKeyedCoding: Bool { true }
init(with request: UNNotificationRequest) {
self.request = request
}
override func decodeObject(forKey key: String) -> Any? {
let fieldKey = FieldKey(rawValue: key)
switch fieldKey {
case .date:
return Date()
case .request:
return request
case .sourceIdentifier, .actionIdentifier, .originIdentifier:
return testIdentifier
case .notification:
return UNNotification(coder: self)
default:
return nil
}
}
}
如果 UNNotificationResponse
值无关紧要,而您只想执行应用委托的那个方法,您可以通过子类化 NSKeyedArchiver
创建模拟来实现此目的,如下所示:
class MockCoder: NSKeyedArchiver {
override func decodeObject(forKey key: String) -> Any { "" }
}
然后你可以这样称呼它:
let notificationMock = try XCTUnwrap(UNNotificationResponse(coder: MockCoder()))
appDelegate.userNotificationCenter(UNUserNotificationCenter.current(), didReceive: notificationMock) { }
您的应用委托的 userNotificationCenter(_:didReceive:withCompletionHandler:)
方法现在将被调用,允许您随心所欲地断言(至少假设没有针对通知本身的断言)。
您可以使用 class_createInstance(UNNotification.classForKeyedArchiver() ...)
并将值转换为 UNNotification
如果您想操纵其 content
和 date
成员,您可以子 class UNNotification
并使用相同的公式«更改 class 名称和投射到你的子class»来创建它,然后你覆盖那些开放的成员和return任何你想要的