Mock UNNotificationResponse& UNNotification(和其他标识为不可用的init()的iOS平台类)

时间:2017-11-22 15:20:54

标签: swift mocking tdd

我需要模仿UNNotificationResponseUNNotification,以便我可以测试我的实现:

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

5 个答案:

答案 0 :(得分:1)

要执行此操作,请执行以下操作。

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

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

答案 1 :(得分:1)

如果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:)方法现在将被调用,使您可以断言内容(至少假定不针对通知本身进行断言)。

答案 2 :(得分:0)

简短回答:你不能!

相反,分解您的func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)实现并测试您从那里调用的方法。

快乐测试:)

答案 3 :(得分:0)

您似乎可以初始化UNNotificationContent个对象。我选择重写我的推送处理方法,以取UNNotificationContentUNNotificationResponse / UNNotification对象。

答案 4 :(得分:0)

在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
        }
    }
}