FIRMessagingRemoteNotificationsProxyTest.m 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  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. @end
  47. #pragma mark - Incomplete App Delegate
  48. @interface IncompleteAppDelegate : NSObject <UIApplicationDelegate>
  49. @end
  50. @implementation IncompleteAppDelegate
  51. @end
  52. #pragma mark - Fake AppDelegate
  53. @interface FakeAppDelegate : NSObject <UIApplicationDelegate>
  54. @property(nonatomic) BOOL remoteNotificationMethodWasCalled;
  55. @property(nonatomic) BOOL remoteNotificationWithFetchHandlerWasCalled;
  56. @end
  57. @implementation FakeAppDelegate
  58. - (void)application:(UIApplication *)application
  59. didReceiveRemoteNotification:(NSDictionary *)userInfo {
  60. self.remoteNotificationMethodWasCalled = YES;
  61. }
  62. - (void)application:(UIApplication *)application
  63. didReceiveRemoteNotification:(NSDictionary *)userInfo
  64. fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  65. self.remoteNotificationWithFetchHandlerWasCalled = YES;
  66. }
  67. @end
  68. #pragma mark - Incompete UNUserNotificationCenterDelegate
  69. @interface IncompleteUserNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>
  70. @end
  71. @implementation IncompleteUserNotificationCenterDelegate
  72. @end
  73. #pragma mark - Fake UNUserNotificationCenterDelegate
  74. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  75. @interface FakeUserNotificationCenterDelegate : NSObject <UNUserNotificationCenterDelegate>
  76. @property(nonatomic) BOOL willPresentWasCalled;
  77. @end
  78. @implementation FakeUserNotificationCenterDelegate
  79. - (void)userNotificationCenter:(UNUserNotificationCenter *)center
  80. willPresentNotification:(UNNotification *)notification
  81. withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))
  82. completionHandler {
  83. self.willPresentWasCalled = YES;
  84. }
  85. @end
  86. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  87. #pragma mark - Local, Per-Test Properties
  88. @interface FIRMessagingRemoteNotificationsProxyTest : XCTestCase
  89. @property(nonatomic, strong) FIRMessagingRemoteNotificationsProxy *proxy;
  90. @property(nonatomic, strong) id mockProxy;
  91. @property(nonatomic, strong) id mockProxyClass;
  92. @property(nonatomic, strong) id mockMessagingClass;
  93. @end
  94. @implementation FIRMessagingRemoteNotificationsProxyTest
  95. - (void)setUp {
  96. [super setUp];
  97. _proxy = [[FIRMessagingRemoteNotificationsProxy alloc] init];
  98. _mockProxy = OCMPartialMock(_proxy);
  99. _mockProxyClass = OCMClassMock([FIRMessagingRemoteNotificationsProxy class]);
  100. // Update +sharedProxy to always return our partial mock of FIRMessagingRemoteNotificationsProxy
  101. OCMStub([_mockProxyClass sharedProxy]).andReturn(_mockProxy);
  102. // Many of our swizzled methods call [FIRMessaging messaging], but we don't need it,
  103. // so just stub it to return nil
  104. _mockMessagingClass = OCMClassMock([FIRMessaging class]);
  105. OCMStub([_mockMessagingClass messaging]).andReturn(nil);
  106. }
  107. - (void)tearDown {
  108. [_mockMessagingClass stopMocking];
  109. _mockMessagingClass = nil;
  110. [_mockProxyClass stopMocking];
  111. _mockProxyClass = nil;
  112. [_mockProxy stopMocking];
  113. _mockProxy = nil;
  114. _proxy = nil;
  115. [super tearDown];
  116. }
  117. #pragma mark - Method Swizzling Tests
  118. - (void)testSwizzlingNonAppDelegate {
  119. id randomObject = @"Random Object that is not an App Delegate";
  120. [self.proxy swizzleAppDelegateMethods:randomObject];
  121. XCTAssertFalse(self.proxy.didSwizzleAppDelegateMethods);
  122. }
  123. - (void)testSwizzlingAppDelegate {
  124. IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init];
  125. [self.proxy swizzleAppDelegateMethods:incompleteAppDelegate];
  126. XCTAssertTrue(self.proxy.didSwizzleAppDelegateMethods);
  127. }
  128. - (void)testSwizzledIncompleteAppDelegateRemoteNotificationMethod {
  129. IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init];
  130. [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate];
  131. SEL selector = @selector(application:didReceiveRemoteNotification:);
  132. XCTAssertTrue([incompleteAppDelegate respondsToSelector:selector]);
  133. [incompleteAppDelegate application:OCMClassMock([UIApplication class])
  134. didReceiveRemoteNotification:@{}];
  135. // Verify our swizzled method was called
  136. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification);
  137. }
  138. // If the remote notification with fetch handler is NOT implemented, we will force-implement
  139. // the backup -application:didReceiveRemoteNotification: method
  140. - (void)testIncompleteAppDelegateRemoteNotificationWithFetchHandlerMethod {
  141. IncompleteAppDelegate *incompleteAppDelegate = [[IncompleteAppDelegate alloc] init];
  142. [self.mockProxy swizzleAppDelegateMethods:incompleteAppDelegate];
  143. SEL remoteNotificationWithFetchHandler =
  144. @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
  145. XCTAssertFalse([incompleteAppDelegate respondsToSelector:remoteNotificationWithFetchHandler]);
  146. SEL remoteNotification = @selector(application:didReceiveRemoteNotification:);
  147. XCTAssertTrue([incompleteAppDelegate respondsToSelector:remoteNotification]);
  148. }
  149. - (void)testSwizzledAppDelegateRemoteNotificationMethods {
  150. FakeAppDelegate *appDelegate = [[FakeAppDelegate alloc] init];
  151. [self.mockProxy swizzleAppDelegateMethods:appDelegate];
  152. [appDelegate application:OCMClassMock([UIApplication class]) didReceiveRemoteNotification:@{}];
  153. // Verify our swizzled method was called
  154. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotification);
  155. // Verify our original method was called
  156. XCTAssertTrue(appDelegate.remoteNotificationMethodWasCalled);
  157. // Now call the remote notification with handler method
  158. [appDelegate application:OCMClassMock([UIApplication class])
  159. didReceiveRemoteNotification:@{}
  160. fetchCompletionHandler:^(UIBackgroundFetchResult result) {}];
  161. // Verify our swizzled method was called
  162. OCMVerify(FCM_swizzle_appDidReceiveRemoteNotificationWithHandler);
  163. // Verify our original method was called
  164. XCTAssertTrue(appDelegate.remoteNotificationWithFetchHandlerWasCalled);
  165. }
  166. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  167. - (void)testListeningForDelegateChangesOnInvalidUserNotificationCenter {
  168. id randomObject = @"Random Object that is not a User Notification Center";
  169. [self.proxy listenForDelegateChangesInUserNotificationCenter:randomObject];
  170. XCTAssertFalse(self.proxy.isObservingUserNotificationDelegateChanges);
  171. }
  172. - (void)testSwizzlingInvalidUserNotificationCenterDelegate {
  173. id randomObject = @"Random Object that is not a User Notification Center Delegate";
  174. [self.proxy swizzleUserNotificationCenterDelegate:randomObject];
  175. XCTAssertFalse(self.proxy.hasSwizzledUserNotificationDelegate);
  176. }
  177. - (void)testSwizzlingUserNotificationsCenterDelegate {
  178. FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init];
  179. [self.proxy swizzleUserNotificationCenterDelegate:delegate];
  180. XCTAssertTrue(self.proxy.hasSwizzledUserNotificationDelegate);
  181. }
  182. // Use a fake delegate that doesn't actually implement the needed delegate method.
  183. // Our swizzled method should still be called.
  184. - (void)testIncompleteUserNotificationCenterDelegateMethod {
  185. // Early exit if running on pre iOS 10
  186. if (![UNNotification class]) {
  187. return;
  188. }
  189. IncompleteUserNotificationCenterDelegate *delegate =
  190. [[IncompleteUserNotificationCenterDelegate alloc] init];
  191. [self.mockProxy swizzleUserNotificationCenterDelegate:delegate];
  192. SEL selector = @selector(userNotificationCenter:willPresentNotification:withCompletionHandler:);
  193. XCTAssertTrue([delegate respondsToSelector:selector]);
  194. // Invoking delegate method should also invoke our swizzled method
  195. // The swizzled method uses the +sharedProxy, which should be
  196. // returning our mocked proxy.
  197. // Use non-nil, proper classes, otherwise our SDK bails out.
  198. [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class])
  199. willPresentNotification:[self generateMockNotification]
  200. withCompletionHandler:^(NSUInteger options) {}];
  201. // Verify our swizzled method was called
  202. OCMVerify(FCM_swizzle_willPresentNotificationWithHandler);
  203. }
  204. // Use an object that does actually implement the needed method. Both should be called.
  205. - (void)testSwizzledUserNotificationsCenterDelegate {
  206. // Early exit if running on pre iOS 10
  207. if (![UNNotification class]) {
  208. return;
  209. }
  210. FakeUserNotificationCenterDelegate *delegate = [[FakeUserNotificationCenterDelegate alloc] init];
  211. [self.mockProxy swizzleUserNotificationCenterDelegate:delegate];
  212. // Invoking delegate method should also invoke our swizzled method
  213. // The swizzled method uses the +sharedProxy, which should be
  214. // returning our mocked proxy.
  215. // Use non-nil, proper classes, otherwise our SDK bails out.
  216. [delegate userNotificationCenter:OCMClassMock([UNUserNotificationCenter class])
  217. willPresentNotification:[self generateMockNotification]
  218. withCompletionHandler:^(NSUInteger options) {}];
  219. // Verify our swizzled method was called
  220. OCMVerify(FCM_swizzle_willPresentNotificationWithHandler);
  221. // Verify our original method was called
  222. XCTAssertTrue(delegate.willPresentWasCalled);
  223. }
  224. - (id)generateMockNotification {
  225. // Stub out: notification.request.content.userInfo
  226. id mockNotification = OCMClassMock([UNNotification class]);
  227. id mockRequest = OCMClassMock([UNNotificationRequest class]);
  228. id mockContent = OCMClassMock([UNNotificationContent class]);
  229. OCMStub([mockContent userInfo]).andReturn(@{});
  230. OCMStub([mockRequest content]).andReturn(mockContent);
  231. OCMStub([mockNotification request]).andReturn(mockRequest);
  232. return mockNotification;
  233. }
  234. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
  235. @end