How to mock UNNotificationResponse in unit tests
Issue #708
The best way to test is to not have to mock at all. The second best way is to have your own abstraction over the things you would like to test, either it is in form of protocol or some function injection.
But in case you want a quick way to test things, and want to test as real as possible, then for some cases we can be creative to mock the real objects.
One practical example is when we have some logic to handle notification, either showing or deep link user to certain screen. From iOS 10, notifications are to be delivered via UNUserNotificationCenterDelegate
1 | 10.0, *) (iOS |
and all we get is UNNotificationResponse
which has no real way to construct it.
1 | 10.0, *) (iOS |
That class inherits from NSCopying
which means it is constructed from NSCoder
, but how do we init it?
1 | let response = UNNotificationResponse(coder: ???) |
NSObject and NSCoder
The trick is, since UNNotificationResponse
is NSObject
subclass, it is key value compliant, and since it is also NSCopying
compliant, we can make a mock coder to construct it
1 | private final class KeyedArchiver: NSKeyedArchiver { |
On iOS 12, we need to add decodeInt64
method, otherwise UNNotificationResponse
init fails. This is not needed on iOS 14
UNNotificationResponse
has a read only UNNotification
, which has a readonly UNNotificationRequest
, which can be constructed from a UNNotificationContent
Luckily UNNotificationContent
has a counterpart UNMutableNotificationContent
Now we can make a simple extension on UNNotificationResponse
to quickly create that object in tests
1 | private extension UNNotificationResponse { |
We can then test like normal
1 | func testResponse() throws { |
decodeObject for key
Another way is to build a proper KeyedArchiver
that checks key and return correct property. Note that we can reuse the same NSKeyedArchiver
to nested properties.
1 | private final class KeyedArchiver: NSKeyedArchiver { |
Updated at 2020-12-07 11:49:55