In my app I wish to assert that notifications have been added in the correct format. I'd normally do this with dependency injection, but I can't think of a way to test the new UNUserNotificationCenter
API.
I started to create a mock object which would capture the notification request:
import Foundation
import UserNotifications
class NotificationCenterMock: UNUserNotificationCenter {
var request: UNNotificationRequest? = nil
override func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)? = nil) {
self.request = request
}
}
However, UNUserNotificationCenter
has no accessible initializers I can't instantiate the mock.
I'm not even sure I can test by adding the notification request and fetching the current notifications, as the tests would need to request permission on the Simulator which would stall the tests. Currently I've refactored the notification logic into a wrapper, so I can at least mock that throughout my application and manually test.
Do I have any better options than manual testing?
You can create a protocol for the methods you are using, and make an extension on UNUserNotificationCenter to conform to it. This protocol would act as a "bridge" between the original UNUserNotificationCenter implementation and your mock object to replace its method implementations.
Here's an example code I wrote in a playground, and works fine:
/* UNUserNotificationCenterProtocol.swift */
// This protocol allows you to use UNUserNotificationCenter, and replace the implementation of its
// methods in you test classes.
protocol UNUserNotificationCenterProtocol: class {
// Declare only the methods that you'll be using.
func add(_ request: UNNotificationRequest,
withCompletionHandler completionHandler: ((Error?) -> Void)?)
}
// The mock class that you'll be using for your test classes. Replace the method contents with your mock
// objects.
class MockNotificationCenter: UNUserNotificationCenterProtocol {
var addRequestExpectation: XCTestExpectation?
func add(_ request: UNNotificationRequest,
withCompletionHandler completionHandler: ((Error?) -> Void)?) {
// Do anything you want here for your tests, fulfill the expectation to pass the test.
addRequestExpectation?.fulfill()
print("Mock center log")
completionHandler?(nil)
}
}
// Must extend UNUserNotificationCenter to conform to this protocol in order to use it in your class.
extension UNUserNotificationCenter: UNUserNotificationCenterProtocol {
// I'm only adding this implementation to show a log message in this example. In order to use the original implementation, don't add it here.
func add(_ request: UNNotificationRequest, withCompletionHandler completionHandler: ((Error?) -> Void)?) {
print("Notification center log")
completionHandler?(nil)
}
}
/* ExampleClass.swift */
class ExampleClass {
// Even though the type is UNUserNotificationCenterProtocol, it will take UNUserNotificationCenter type
// because of the extension above.
var notificationCenter: UNUserNotificationCenterProtocol = UNUserNotificationCenter.current()
func doSomething() {
// Create a request.
let content = UNNotificationContent()
let request = UNNotificationRequest(identifier: "Request",
content: content,
trigger: nil)
notificationCenter.add(request) { (error: Error?) in
// completion handler code
}
}
}
let exampleClass = ExampleClass()
exampleClass.doSomething() // This should log "Notification center log"
EDITED:
/* TestClass.Swift (unit test class) */
class TestClass {
// Class being tested
var exampleClass: ExampleClass!
// Create your mock class.
var mockNotificationCenter = MockNotificationCenter()
func setUp() {
super.setUp()
exampleClass = ExampleClass()
exampleClass.notificationCenter = mockNotificationCenter
}
func testDoSomething() {
mockNotificationCenter.addRequestExpectation = expectation(description: "Add request should've been called")
exampleClass.doSomething()
waitForExpectations(timeout: 1)
}
}
// Once you run the test, the expectation will be called and "Mock Center Log" will be printed
Keep in mind that every time you use a new method, you'll have to add it to the protocol, or the compiler will complain.
Hope this helps!
You can utilize UNUserNotificationCenter
, then setValue
on the returned settings
UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { settings in
let status: UNAuthorizationStatus = .authorized
settings.setValue(status.rawValue, forKey: "authorizationStatus")
completionHandler(settings)
})
I managed to mock UNNotificationSettings
using below
let object = UIView()
let data = try! NSKeyedArchiver.archivedData(withRootObject: object, requiringSecureCoding: false)
let coder = try! NSKeyedUnarchiver(forReadingFrom: data)
let setting = UNNotificationSettings(coder: coder)
setting?.setValue(UNAuthorizationStatus.notDetermined.rawValue, forKey: "authorizationStatus")
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