FIRMessagingRemoteNotificationsProxyTest.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  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/UserNotifications.h>
  18. #endif
  19. #import <XCTest/XCTest.h>
  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. #ifdef BUG_1451
  138. SEL selector = @selector(application:didReceiveRemoteNotification:);
  139. XCTAssertTrue([incompleteAppDelegate respondsToSelector:selector]);
  140. [incompleteAppDelegate application:OCMClassMock([UIApplication class])
  141. didReceiveRemoteNotification:@{}];
  142. // Verify our swizzled method was called
  143. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification);
  144. #endif
  145. }
  146. // If the remote notification with fetch handler is NOT implemented, we will force-implement
  147. // the backup -application:didReceiveRemoteNotification: method
  148. - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod {
  149. IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init];
  150. [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate];
  151. SEL remoteNotificationWithFetchHandler =
  152. @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
  153. XCTAssertFalse([incompleteAppDelegate respondsToSelector:remoteNotificationWithFetchHandler]);
  154. #ifdef BUG_1451
  155. SEL remoteNotification = @selector(application:didReceiveRemoteNotification:);
  156. XCTAssertTrue([incompleteAppDelegate respondsToSelector:remoteNotification]);
  157. #endif
  158. }
  159. - (void)testSwizzledAppDelegateRemoteNotificationMethods {
  160. FakeAppDelegate *appDelegate = [[FakeAppDelegate alloc] init];
  161. [self.mockProxy swizzleAppDelegateMethods:appDelegate];
  162. [appDelegate application:OCMClassMock([UIApplication class]) didReceiveRemoteNotification:@{}];
  163. // Verify our swizzled method was called
  164. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification);
  165. // Verify our original method was called
  166. XCTAssertTrue(appDelegate.remoteNotificationMethodWasCalled);
  167. // Now call the remote notification with handler method
  168. [appDelegate application:OCMClassMock([UIApplication class])
  169. didReceiveRemoteNotification:@{}
  170. fetchCompletionHandler:^(UIBackgroundFetchResult result) {}];
  171. // Verify our swizzled method was called
  172. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler);
  173. // Verify our original method was called
  174. XCTAssertTrue(appDelegate.remoteNotificationWithFetchHandlerWasCalled);
  175. }
  176. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  177. - (void)testListeningForDelegateChangesOnInvalidUserNotificationCenter {
  178. id randomObject = @"Random Object that is not a User Notification Center";
  179. [self.proxy listenForDelegateChangesInUserNotificationCenter:randomObject];
  180. XCTAssertFalse(self.proxy.isObservingUserNotificationDelegateChanges);
  181. }
  182. - (void)testSwizzlingInvalidUserNotificationCenterDelegate {
  183. id randomObject = @"Random Object that is not a User Notification Center Delegate";
  184. [self.proxy swizzleUserNotificationCenterDelegate:randomObject];
  185. XCTAssertFalse(self.proxy.hasSwizzledUserNotificationDelegate);
  186. }
  187. - (void)testSwizzlingUserNotificationsCenterDelegate {
  188. FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init];
  189. [self.proxy swizzleUserNotificationCenterDelegate:delegate];
  190. XCTAssertTrue(self.proxy.hasSwizzledUserNotificationDelegate);
  191. }
  192. // Use a fake delegate that doesn't actually implement the needed delegate method.
  193. // Our swizzled method should not be called.
  194. - (void)testIncompleteUserNotificationCenterDelegateMethod {
  195. // Early exit if running on pre iOS 10
  196. if (![UNNotification class]) {
  197. return;
  198. }
  199. IncompleteUserNotificationCenterDelegate *delegate =
  200. [[IncompleteUserNotificationCenterDelegate alloc] init];
  201. [self.mockProxy swizzleUserNotificationCenterDelegate:delegate];
  202. // Because the incomplete delete does not implement either of the optional delegate methods, we
  203. // should swizzle nothing. If we had swizzled them, then respondsToSelector: would return YES
  204. // even though the delegate does not implement the methods.
  205. SEL willPresentSelector = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:);
  206. XCTAssertFalse([delegate respondsToSelector:willPresentSelector]);
  207. SEL didReceiveResponseSelector =
  208. @selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
  209. XCTAssertFalse([delegate respondsToSelector:didReceiveResponseSelector]);
  210. }
  211. // Use an object that does actually implement the optional methods. Both should be called.
  212. - (void)testSwizzledUserNotificationsCenterDelegate {
  213. // Early exit if running on pre iOS 10
  214. if (![UNNotification class]) {
  215. return;
  216. }
  217. FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init];
  218. [self.mockProxy swizzleUserNotificationCenterDelegate:delegate];
  219. // Invoking delegate method should also invoke our swizzled method
  220. // The swizzled method uses the +sharedProxy, which should be
  221. // returning our mocked proxy.
  222. // Use non-nil, proper classes, otherwise our SDK bails out.
  223. [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class])
  224. willPresentNotification:[self generateMockNotification]
  225. withCompletionHandler:^(NSUInteger options) {}];
  226. // Verify our swizzled method was called
  227. OCMVerify(FCM_swizzle_willPresentNotificationWithHandler);
  228. // Verify our original method was called
  229. XCTAssertTrue(delegate.willPresentWasCalled);
  230. [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class])
  231. didReceiveNotificationResponse:[self generateMockNotificationResponse]
  232. withCompletionHandler:^{}];
  233. // Verify our swizzled method was called
  234. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler);
  235. // Verify our original method was called
  236. XCTAssertTrue(delegate.didReceiveResponseWasCalled);
  237. }
  238. - (id)generateMockNotification {
  239. // Stub out: notification.request.content.userInfo
  240. id mockNotification = OCMClassMock([UNNotification class]);
  241. id mockRequest = OCMClassMock([UNNotificationRequest class]);
  242. id mockContent = OCMClassMock([UNNotificationContent class]);
  243. OCMStub([mockContent userInfo]).andReturn(@{});
  244. OCMStub([mockRequest content]).andReturn(mockContent);
  245. OCMStub([mockNotification request]).andReturn(mockRequest);
  246. return mockNotification;
  247. }
  248. - (id)generateMockNotificationResponse {
  249. // Stub out: response.[mock notification above]
  250. id mockNotificationResponse = OCMClassMock([UNNotificationResponse class]);
  251. id mockNotification = [self generateMockNotification];
  252. OCMStub([mockNotificationResponse notification]).andReturn(mockNotification);
  253. return mockNotificationResponse;
  254. }
  255. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  256. @end