代码之家  ›  专栏  ›  技术社区  ›  Andrew Ebling

模拟UNNotificationResponse和UNNotification(以及init()标记为不可用的其他iOS平台类)

  •  11
  • Andrew Ebling  · 技术社区  · 7 年前

    我需要嘲笑 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,我无法修改它以采用我要编写的协议。

    6 回复  |  直到 7 年前
        1
  •  10
  •   acastano    6 年前

    要做到这一点,请执行以下操作。

    在调试时获取对象的真实示例,并使用模拟器保存在文件系统中。

    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中找到该对象,并将该文件添加到与测试类相同的目标中。

    现在在测试中取消归档。

    
    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
    
    
        2
  •  1
  •   Paul Peelen    4 年前

    简单回答:你不能!

    相反,分解您的

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)
    

    然后测试从那里调用的方法。

    快乐测试:)

        3
  •  1
  •   egarc    6 年前

    看起来您可以初始化 UNNotificationContent 物体。我选择重做推送处理方法 未通知内容 对象而不是 UNNotificationResponse / UNNotification .

        4
  •  1
  •   sgl0v    4 年前

    在iOS上实现推送通知的单元测试时,我使用了下一个扩展来创建UNNotificationResponse和UNNotification实例:

    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
        }
    }
    
    1. 从文件加载推送通知负载
    2. 使用userInfo中的指定参数创建UNNotificationRequest实例
    3. 使用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
            }
        }
    }
    
        5
  •  1
  •   Andrew Dunn    4 年前

    如果 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:) 方法现在将被调用,允许您对自己的内容进行断言(至少假设没有针对通知本身的断言)。

        6
  •  0
  •   Bruno Garelli    3 年前

    您可以使用 class_createInstance(UNNotification.classForKeyedArchiver() ...) 并将其价值视为不被注意

    如果你想操纵它 content date 您可以子类化的成员 UNNotification 使用相同的公式更改类名并强制转换到子类来创建它,然后覆盖那些打开的成员并返回所需的任何内容