I need to mock UNNotificationResponse
and UNNotification
so that I can test my implementation of:
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)
However I can't usefully subclass these classes because init()
is specifically marked as unavailable
, resulting in compilation errors like this if I try:
/Path/to/PushClientTests.swift:38:5: Cannot override 'init' which has been marked unavailable
What alternate approaches can be taken here? I look into going down the Protocol Oriented Programming route, however since I do not control the API being called, I can't modify it to take the protocols I'd write.
To do it you do the following.
Get a real example of the object while debugging and save in file system using your simulator.
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)
Find the object in your Mac and add the file in the same target as the test class.
Now unarchive in your test.
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
I've used the next extension to create UNNotificationResponse and UNNotification instances while implementing unit tests for push notifications on 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
}
}
Here are the functions I've used above:
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
}
}
}
Short answer: You can't!
Instead, decompose your implementation of
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Swift.Void)
and test the methods you call from there, instead.
Happy testing :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With