FIRMessagingRemoteNotificationsProxyTest.m 12 KB

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