FIRMessagingRemoteNotificationsProxyTest.m 12 KB

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