FIRMessagingRemoteNotificationsProxyTest.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  17. @import UserNotifications;
  18. #endif
  19. @import XCTest;
  20. #import <OCMock/OCMock.h>
  21. #import "FIRMessaging.h"
  22. #import "FIRMessagingRemoteNotificationsProxy.h"
  23. #pragma mark - Expose Internal Methods for Testing
  24. // Expose some internal properties and methods here, in order to test
  25. @interface FIRMessagingRemoteNotificationsProxy ()
  26. @property(readonly, nonatomic) BOOL didSwizzleMethods;
  27. @property(readonly, nonatomic) BOOL didSwizzleAppDelegateMethods;
  28. @property(readonly, nonatomic) BOOL hasSwizzledUserNotificationDelegate;
  29. @property(readonly, nonatomic) BOOL isObservingUserNotificationDelegateChanges;
  30. @property(strong, readonly, nonatomic) id userNotificationCenter;
  31. @property(strong, readonly, nonatomic) id currentUserNotificationCenterDelegate;
  32. + (instancetype)sharedProxy;
  33. - (BOOL)swizzleAppDelegateMethods:(id<UIApplicationDelegate>)appDelegate;
  34. - (void)listenForDelegateChangesInUserNotificationCenter:(id)notificationCenter;
  35. - (void)swizzleUserNotificationCenterDelegate:(id)delegate;
  36. - (void)unswizzleUserNotificationCenterDelegate:(id)delegate;
  37. void FCM_swizzle_appDidReceiveRemoteNotification(id self,
  38. SEL _cmd,
  39. UIApplication *app,
  40. NSDictionary *userInfo);
  41. void FCM_swizzle_appDidReceiveRemoteNotificationWithHandler(
  42. id self, SEL _cmd, UIApplication *app, NSDictionary *userInfo,
  43. void (^handler)(UIBackgroundFetchResult));
  44. void FCM_swizzle_willPresentNotificationWithHandler(
  45. id self, SEL _cmd, id center, id notification, void (^handler)(NSUInteger));
  46. void FCM_swizzle_didReceiveNotificationResponseWithHandler(
  47. id self, SEL _cmd, id center, id response, void (^handler)());
  48. @end
  49. #pragma mark - Incomplete App Delegate
  50. @interface IncompleteAppDelegate : NSObject <UIApplicationDelegate>
  51. @end
  52. @implementation IncompleteAppDelegate
  53. @end
  54. #pragma mark - Fake AppDelegate
  55. @interface FakeAppDelegate : NSObject <UIApplicationDelegate>
  56. @property(nonatomic) BOOL remoteNotificationMethodWasCalled;
  57. @property(nonatomic) BOOL remoteNotificationWithFetchHandlerWasCalled;
  58. @end
  59. @implementation FakeAppDelegate
  60. - (void)application:(UIApplication *)application
  61. didReceiveRemoteNotification:(NSDictionary *)userInfo {
  62. self.remoteNotificationMethodWasCalled = YES;
  63. }
  64. - (void)application:(UIApplication *)application
  65. didReceiveRemoteNotification:(NSDictionary *)userInfo
  66. fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  67. self.remoteNotificationWithFetchHandlerWasCalled = YES;
  68. }
  69. @end
  70. #pragma mark - Incompete UNUserNotificationCenterDelegate
  71. @interface IncompleteUserNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>
  72. @end
  73. @implementation IncompleteUserNotificationCenterDelegate
  74. @end
  75. #pragma mark - Fake UNUserNotificationCenterDelegate
  76. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  77. @interface FakeUserNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>
  78. @property(nonatomic) BOOL willPresentWasCalled;
  79. @property(nonatomic) BOOL didReceiveResponseWasCalled;
  80. @end
  81. @implementation FakeUserNotificationCenterDelegate
  82. - (void)userNotificationCenter:(UNUserNotificationCenter *)center
  83. willPresentNotification:(UNNotification *)notification
  84. withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))
  85. completionHandler {
  86. self.willPresentWasCalled = YES;
  87. }
  88. - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
  89. self.didReceiveResponseWasCalled = YES;
  90. }
  91. @end
  92. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  93. #pragma mark - Local, Per-Test Properties
  94. @interface FIRMessagingRemoteNotificationsProxyTest : XCTestCase
  95. @property(nonatomic, strong) FIRMessagingRemoteNotificationsProxy *proxy;
  96. @property(nonatomic, strong) id mockProxy;
  97. @property(nonatomic, strong) id mockProxyClass;
  98. @property(nonatomic, strong) id mockMessagingClass;
  99. @end
  100. @implementation FIRMessagingRemoteNotificationsProxyTest
  101. - (void)setUp {
  102. [super setUp];
  103. _proxy = [[FIRMessagingRemoteNotificationsProxy alloc] init];
  104. _mockProxy = OCMPartialMock(_proxy);
  105. _mockProxyClass = OCMClassMock([FIRMessagingRemoteNotificationsProxy class]);
  106. // Update +sharedProxy to always return our partial mock of FIRMessagingRemoteNotificationsProxy
  107. OCMStub([_mockProxyClass sharedProxy]).andReturn(_mockProxy);
  108. // Many of our swizzled methods call [FIRMessaging messaging], but we don't need it,
  109. // so just stub it to return nil
  110. _mockMessagingClass = OCMClassMock([FIRMessaging class]);
  111. OCMStub([_mockMessagingClass messaging]).andReturn(nil);
  112. }
  113. - (void)tearDown {
  114. [_mockMessagingClass stopMocking];
  115. _mockMessagingClass = nil;
  116. [_mockProxyClass stopMocking];
  117. _mockProxyClass = nil;
  118. [_mockProxy stopMocking];
  119. _mockProxy = nil;
  120. _proxy = nil;
  121. [super tearDown];
  122. }
  123. #pragma mark - Method Swizzling Tests
  124. - (void)testSwizzlingNonAppDelegate {
  125. id randomObject = @"Random Object that is not an App Delegate";
  126. [self.proxy swizzleAppDelegateMethods:randomObject];
  127. XCTAssertFalse(self.proxy.didSwizzleAppDelegateMethods);
  128. }
  129. - (void)testSwizzlingAppDelegate {
  130. IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init];
  131. [self.proxy swizzleAppDelegateMethods:incompleteAppDelegate];
  132. XCTAssertTrue(self.proxy.didSwizzleAppDelegateMethods);
  133. }
  134. - (void)testSwizzledIncompleteAppDelegateRemoteNotificationMethod {
  135. IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init];
  136. [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate];
  137. SEL selector = @selector(application:didReceiveRemoteNotification:);
  138. XCTAssertTrue([incompleteAppDelegate respondsToSelector:selector]);
  139. [incompleteAppDelegate application:OCMClassMock([UIApplication class])
  140. didReceiveRemoteNotification:@{}];
  141. // Verify our swizzled method was called
  142. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification);
  143. }
  144. // If the remote notification with fetch handler is NOT implemented, we will force-implement
  145. // the backup -application:didReceiveRemoteNotification: method
  146. - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod {
  147. IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init];
  148. [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate];
  149. SEL remoteNotificationWithFetchHandler =
  150. @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
  151. XCTAssertFalse([incompleteAppDelegate respondsToSelector:remoteNotificationWithFetchHandler]);
  152. SEL remoteNotification = @selector(application:didReceiveRemoteNotification:);
  153. XCTAssertTrue([incompleteAppDelegate respondsToSelector:remoteNotification]);
  154. }
  155. - (void)testSwizzledAppDelegateRemoteNotificationMethods {
  156. FakeAppDelegate *appDelegate = [[FakeAppDelegate alloc] init];
  157. [self.mockProxy swizzleAppDelegateMethods:appDelegate];
  158. [appDelegate application:OCMClassMock([UIApplication class]) didReceiveRemoteNotification:@{}];
  159. // Verify our swizzled method was called
  160. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification);
  161. // Verify our original method was called
  162. XCTAssertTrue(appDelegate.remoteNotificationMethodWasCalled);
  163. // Now call the remote notification with handler method
  164. [appDelegate application:OCMClassMock([UIApplication class])
  165. didReceiveRemoteNotification:@{}
  166. fetchCompletionHandler:^(UIBackgroundFetchResult result) {}];
  167. // Verify our swizzled method was called
  168. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler);
  169. // Verify our original method was called
  170. XCTAssertTrue(appDelegate.remoteNotificationWithFetchHandlerWasCalled);
  171. }
  172. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  173. - (void)testListeningForDelegateChangesOnInvalidUserNotificationCenter {
  174. id randomObject = @"Random Object that is not a User Notification Center";
  175. [self.proxy listenForDelegateChangesInUserNotificationCenter:randomObject];
  176. XCTAssertFalse(self.proxy.isObservingUserNotificationDelegateChanges);
  177. }
  178. - (void)testSwizzlingInvalidUserNotificationCenterDelegate {
  179. id randomObject = @"Random Object that is not a User Notification Center Delegate";
  180. [self.proxy swizzleUserNotificationCenterDelegate:randomObject];
  181. XCTAssertFalse(self.proxy.hasSwizzledUserNotificationDelegate);
  182. }
  183. - (void)testSwizzlingUserNotificationsCenterDelegate {
  184. FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init];
  185. [self.proxy swizzleUserNotificationCenterDelegate:delegate];
  186. XCTAssertTrue(self.proxy.hasSwizzledUserNotificationDelegate);
  187. }
  188. // Use a fake delegate that doesn't actually implement the needed delegate method.
  189. // Our swizzled method should not be called.
  190. - (void)testIncompleteUserNotificationCenterDelegateMethod {
  191. // Early exit if running on pre iOS 10
  192. if (![UNNotification class]) {
  193. return;
  194. }
  195. IncompleteUserNotificationCenterDelegate *delegate =
  196. [[IncompleteUserNotificationCenterDelegate alloc] init];
  197. [self.mockProxy swizzleUserNotificationCenterDelegate:delegate];
  198. // Because the incomplete delete does not implement either of the optional delegate methods, we
  199. // should swizzle nothing. If we had swizzled them, then respondsToSelector: would return YES
  200. // even though the delegate does not implement the methods.
  201. SEL willPresentSelector = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:);
  202. XCTAssertFalse([delegate respondsToSelector:willPresentSelector]);
  203. SEL didReceiveResponseSelector =
  204. @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
  205. XCTAssertFalse([delegate respondsToSelector:didReceiveResponseSelector]);
  206. }
  207. // Use an object that does actually implement the optional methods. Both should be called.
  208. - (void)testSwizzledUserNotificationsCenterDelegate {
  209. // Early exit if running on pre iOS 10
  210. if (![UNNotification class]) {
  211. return;
  212. }
  213. FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init];
  214. [self.mockProxy swizzleUserNotificationCenterDelegate:delegate];
  215. // Invoking delegate method should also invoke our swizzled method
  216. // The swizzled method uses the +sharedProxy, which should be
  217. // returning our mocked proxy.
  218. // Use non-nil, proper classes, otherwise our SDK bails out.
  219. [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class])
  220. willPresentNotification:[self generateMockNotification]
  221. withCompletionHandler:^(NSUInteger options) {}];
  222. // Verify our swizzled method was called
  223. OCMVerify(FCM_swizzle_willPresentNotificationWithHandler);
  224. // Verify our original method was called
  225. XCTAssertTrue(delegate.willPresentWasCalled);
  226. [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class])
  227. didReceiveNotificationResponse:[self generateMockNotificationResponse]
  228. withCompletionHandler:^{}];
  229. // Verify our swizzled method was called
  230. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler);
  231. // Verify our original method was called
  232. XCTAssertTrue(delegate.didReceiveResponseWasCalled);
  233. }
  234. - (id)generateMockNotification {
  235. // Stub out: notification.request.content.userInfo
  236. id mockNotification = OCMClassMock([UNNotification class]);
  237. id mockRequest = OCMClassMock([UNNotificationRequest class]);
  238. id mockContent = OCMClassMock([UNNotificationContent class]);
  239. OCMStub([mockContent userInfo]).andReturn(@{});
  240. OCMStub([mockRequest content]).andReturn(mockContent);
  241. OCMStub([mockNotification request]).andReturn(mockRequest);
  242. return mockNotification;
  243. }
  244. - (id)generateMockNotificationResponse {
  245. // Stub out: response.[mock notification above]
  246. id mockNotificationResponse = OCMClassMock([UNNotificationResponse class]);
  247. id mockNotification = [self generateMockNotification];
  248. OCMStub([mockNotificationResponse notification]).andReturn(mockNotification);
  249. return mockNotificationResponse;
  250. }
  251. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  252. @end