GULAppDelegateSwizzlerTest.m 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304
  1. // Copyright 2018 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <GoogleUtilities/GULAppDelegateSwizzler.h>
  15. #import "GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h"
  16. #import <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import <objc/runtime.h>
  19. #if (defined(__IPHONE_9_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0))
  20. #define SDK_HAS_USERACTIVITY 1
  21. #endif
  22. /** Plist key that allows Firebase developers to disable App Delegate Proxying. Source of truth is
  23. * the GULAppDelegateSwizzler class.
  24. */
  25. static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey =
  26. @"FirebaseAppDelegateProxyEnabled";
  27. /** Plist key that allows non-Firebase developers to disable App Delegate Proxying. Source of truth
  28. * is the GULAppDelegateSwizzler class.
  29. */
  30. static NSString *const kGULGoogleAppDelegateProxyEnabledPlistKey =
  31. @"GoogleUtilitiesAppDelegateProxyEnabled";
  32. #pragma mark - GULTestAppDelegate
  33. /** This class conforms to the application delegate protocol and is there to be able to test the
  34. * App Delegate Swizzler's behavior.
  35. */
  36. @interface GULTestAppDelegate : NSObject <GULApplicationDelegate> {
  37. @public // Because we want to access the ivars from outside the class like obj->ivar for testing.
  38. /** YES if the application:openURL:options: was called on an instance, NO otherwise. */
  39. BOOL _isOpenURLOptionsMethodCalled;
  40. /** Contains the backgroundSessionID that was passed to the
  41. * application:handleEventsForBackgroundURLSession:completionHandler: method.
  42. */
  43. NSString *_backgroundSessionID;
  44. /** YES if init was called. Used for memory layout testing after isa swizzling. */
  45. BOOL _isInitialized;
  46. /** An arbitrary number. Used for memory layout testing after isa swizzling. */
  47. int _arbitraryNumber;
  48. }
  49. /** A URL property that is set by the app delegate methods, which is then used to verify if the app
  50. * delegate methods were properly called.
  51. */
  52. @property(nonatomic, strong) NSURL *url;
  53. @property(nonatomic, strong) NSDictionary<NSString *, id> *openURLOptions;
  54. @property(nonatomic, strong) NSString *openURLSourceApplication;
  55. @property(nonatomic, strong) id openURLAnnotation;
  56. @property(nonatomic, strong) NSUserActivity *userActivity;
  57. @property(nonatomic, strong) NSData *remoteNotificationsDeviceToken;
  58. @property(nonatomic, strong) NSError *failToRegisterForRemoteNotificationsError;
  59. @property(nonatomic, strong) NSDictionary *remoteNotification;
  60. #if TARGET_OS_IOS || TARGET_OS_TV
  61. @property(nonatomic, copy) void (^remoteNotificationCompletionHandler)(UIBackgroundFetchResult);
  62. #endif // TARGET_OS_IOS || TARGET_OS_TV
  63. /**
  64. * The application is set each time a GULApplicationDelegate method is called
  65. */
  66. @property(nonatomic, weak) GULApplication *application;
  67. @end
  68. @implementation GULTestAppDelegate
  69. // TODO: The static BOOLs below being accurate is dependent on the runtime loading
  70. // GULTestAppDelegate before GULAppDelegateSwizzlerTest. It works, but it might be a good idea to
  71. // figure a way to make this more deterministic.
  72. /** YES if GULTestAppDelegate responds to application:openURL:sourceApplication:annotation:, NO
  73. * otherwise.
  74. */
  75. #if TARGET_OS_IOS || TARGET_OS_TV
  76. static BOOL gRespondsToOpenURLHandler_iOS8;
  77. #endif
  78. /** YES if GULTestAppDelegate responds to application:openURL:options:, NO otherwise. */
  79. static BOOL gRespondsToOpenURLHandler_iOS9;
  80. /** YES if GULTestAppDelegate responds to application:continueUserActivity:restorationHandler:, NO
  81. * otherwise.
  82. */
  83. static BOOL gRespondsToContinueUserActivity;
  84. /** YES if GULTestAppDelegate responds to
  85. * application:handleEventsForBackgroundURLSession:completionHandler:, NO otherwise.
  86. */
  87. static BOOL gRespondsToHandleBackgroundSession;
  88. + (void)load {
  89. #pragma clang diagnostic push
  90. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  91. // Before being proxied, it should be only be able to respond to
  92. // application:openURL:sourceApplication:annotation:.
  93. #if TARGET_OS_IOS || TARGET_OS_TV
  94. gRespondsToOpenURLHandler_iOS8 = [self
  95. instancesRespondToSelector:@selector(application:openURL:sourceApplication:annotation:)];
  96. #endif
  97. gRespondsToOpenURLHandler_iOS9 =
  98. [self instancesRespondToSelector:@selector(application:openURL:options:)];
  99. gRespondsToHandleBackgroundSession =
  100. [self instancesRespondToSelector:@selector(application:
  101. handleEventsForBackgroundURLSession:completionHandler:)];
  102. gRespondsToContinueUserActivity = [self
  103. instancesRespondToSelector:@selector(application:continueUserActivity:restorationHandler:)];
  104. #pragma clang diagnostic pop
  105. }
  106. - (instancetype)init {
  107. self = [super init];
  108. if (self) {
  109. _isOpenURLOptionsMethodCalled = NO;
  110. _isInitialized = YES;
  111. _arbitraryNumber = 123456789;
  112. _backgroundSessionID = @"randomSessionID";
  113. _url = nil;
  114. }
  115. return self;
  116. }
  117. - (BOOL)application:(GULApplication *)app
  118. openURL:(NSURL *)url
  119. options:(NSDictionary<NSString *, id> *)options {
  120. self.application = app;
  121. self.url = url;
  122. self.openURLOptions = options;
  123. _isOpenURLOptionsMethodCalled = YES;
  124. return NO;
  125. }
  126. #if TARGET_OS_IOS
  127. - (BOOL)application:(GULApplication *)application
  128. openURL:(NSURL *)url
  129. sourceApplication:(NSString *)sourceApplication
  130. annotation:(id)annotation {
  131. self.application = application;
  132. self.url = url;
  133. self.openURLSourceApplication = sourceApplication;
  134. self.openURLAnnotation = annotation;
  135. return NO;
  136. }
  137. #endif // TARGET_OS_IOS
  138. - (BOOL)application:(GULApplication *)application
  139. continueUserActivity:(NSUserActivity *)userActivity
  140. restorationHandler:(void (^)(NSArray<id<GULUserActivityRestoring>> *__nullable
  141. restorableObjects))restorationHandler {
  142. self.application = application;
  143. self.userActivity = userActivity;
  144. return NO;
  145. }
  146. - (void)application:(GULApplication *)application
  147. handleEventsForBackgroundURLSession:(nonnull NSString *)identifier
  148. completionHandler:(nonnull void (^)(void))completionHandler {
  149. self.application = application;
  150. _backgroundSessionID = identifier;
  151. }
  152. - (void)application:(GULApplication *)application
  153. didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  154. self.application = application;
  155. self.remoteNotificationsDeviceToken = deviceToken;
  156. }
  157. - (void)application:(GULApplication *)application
  158. didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
  159. self.application = application;
  160. self.failToRegisterForRemoteNotificationsError = error;
  161. }
  162. #pragma clang diagnostic push
  163. #pragma clang diagnostic ignored "-Wdeprecated-implementations"
  164. - (void)application:(GULApplication *)application
  165. didReceiveRemoteNotification:(NSDictionary *)userInfo {
  166. self.application = application;
  167. self.remoteNotification = userInfo;
  168. }
  169. #pragma clang diagnostic pop
  170. #if TARGET_OS_IOS || TARGET_OS_TV
  171. - (void)application:(GULApplication *)application
  172. didReceiveRemoteNotification:(NSDictionary *)userInfo
  173. fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
  174. self.application = application;
  175. self.remoteNotification = userInfo;
  176. self.remoteNotificationCompletionHandler = completionHandler;
  177. }
  178. #endif // TARGET_OS_IOS || TARGET_OS_TV
  179. // These are methods to test whether changing the class still maintains behavior that the app
  180. // delegate proxy shouldn't have modified.
  181. - (NSString *)someArbitraryMethod {
  182. return @"blabla";
  183. }
  184. + (int)someNumber {
  185. return 890;
  186. }
  187. @end
  188. @interface GULEmptyTestAppDelegate : NSObject <GULApplicationDelegate>
  189. @end
  190. @implementation GULEmptyTestAppDelegate
  191. @end
  192. #pragma mark - Interceptor class
  193. /** This is a class used to test whether interceptors work with the App Delegate Swizzler. */
  194. @interface GULTestInterceptorAppDelegate : NSObject <GULApplicationDelegate>
  195. /** URL sent to application:openURL:options:. */
  196. @property(nonatomic, copy) NSURL *URLForIOS9;
  197. /** URL sent to application:openURL:sourceApplication:annotation:. */
  198. @property(nonatomic, copy) NSURL *URLForIOS8;
  199. /** The NSUserActivity sent to application:continueUserActivity:restorationHandler:. */
  200. @property(nonatomic, copy) NSUserActivity *userActivity;
  201. @end
  202. @implementation GULTestInterceptorAppDelegate
  203. - (BOOL)application:(GULApplication *)app
  204. openURL:(NSURL *)url
  205. options:(NSDictionary<NSString *, id> *)options {
  206. _URLForIOS9 = [url copy];
  207. return YES;
  208. }
  209. #if TARGET_OS_IOS
  210. - (BOOL)application:(GULApplication *)application
  211. openURL:(nonnull NSURL *)url
  212. sourceApplication:(nullable NSString *)sourceApplication
  213. annotation:(nonnull id)annotation {
  214. _URLForIOS8 = [url copy];
  215. return YES;
  216. }
  217. #endif // TARGET_OS_IOS
  218. #if SDK_HAS_USERACTIVITY
  219. - (BOOL)application:(GULApplication *)application
  220. continueUserActivity:(NSUserActivity *)userActivity
  221. restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *__nullable
  222. restorableObjects))restorationHandler {
  223. _userActivity = userActivity;
  224. return YES;
  225. }
  226. #endif // SDK_HAS_USERACTIVITY
  227. @end
  228. @interface GULAppDelegateSwizzlerTest : XCTestCase
  229. @property(nonatomic, strong) id mockSharedApplication;
  230. @end
  231. @implementation GULAppDelegateSwizzlerTest
  232. - (void)setUp {
  233. [super setUp];
  234. self.mockSharedApplication = OCMPartialMock([GULApplication sharedApplication]);
  235. }
  236. - (void)tearDown {
  237. [GULAppDelegateSwizzler clearInterceptors];
  238. [GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken];
  239. self.mockSharedApplication = nil;
  240. [super tearDown];
  241. }
  242. - (void)testNotAppDelegateIsNotSwizzled {
  243. NSObject *notAppDelegate = [[NSObject alloc] init];
  244. OCMStub([self.mockSharedApplication delegate]).andReturn(notAppDelegate);
  245. [GULAppDelegateSwizzler proxyOriginalDelegate];
  246. XCTAssertEqualObjects(NSStringFromClass([notAppDelegate class]), @"NSObject");
  247. }
  248. /** Tests proxying an object that responds to application delegate protocol and makes sure that
  249. * it is isa swizzled and that the object after proxying responds to the expected methods
  250. * and doesn't have its ivars modified.
  251. */
  252. - (void)testProxyAppDelegate {
  253. GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
  254. OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
  255. size_t sizeBefore = class_getInstanceSize([GULTestAppDelegate class]);
  256. Class realAppDelegateClassBefore = [realAppDelegate class];
  257. // Create the proxy.
  258. [GULAppDelegateSwizzler proxyOriginalDelegate];
  259. XCTAssertTrue([realAppDelegate isKindOfClass:[GULTestAppDelegate class]]);
  260. NSString *newClassName = NSStringFromClass([realAppDelegate class]);
  261. XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
  262. // It is no longer GULTestAppDelegate class instance.
  263. XCTAssertFalse([realAppDelegate isMemberOfClass:[GULTestAppDelegate class]]);
  264. size_t sizeAfter = class_getInstanceSize([realAppDelegate class]);
  265. // Class size must stay the same.
  266. XCTAssertEqual(sizeBefore, sizeAfter);
  267. // After being proxied, it should be able to respond to the required method selector.
  268. #if TARGET_OS_IOS
  269. XCTAssertTrue([realAppDelegate
  270. respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]);
  271. #endif // TARGET_OS_IOS
  272. XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
  273. continueUserActivity:restorationHandler:)]);
  274. XCTAssertTrue([realAppDelegate
  275. respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
  276. XCTAssertTrue([realAppDelegate
  277. respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
  278. XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
  279. didReceiveRemoteNotification:)]);
  280. #if TARGET_OS_IOS || TARGET_OS_TV
  281. XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]);
  282. XCTAssertTrue([realAppDelegate
  283. respondsToSelector:@selector(application:
  284. handleEventsForBackgroundURLSession:completionHandler:)]);
  285. XCTAssertTrue([realAppDelegate
  286. respondsToSelector:@selector(application:
  287. didReceiveRemoteNotification:fetchCompletionHandler:)]);
  288. #endif // TARGET_OS_IOS || TARGET_OS_TV
  289. // Make sure that the class has changed.
  290. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore);
  291. // Make sure that the ivars are not changed in memory as the subclass is created. Directly
  292. // accessing the ivars should not crash.
  293. XCTAssertEqual(realAppDelegate->_arbitraryNumber, 123456789);
  294. XCTAssertEqual(realAppDelegate->_isInitialized, 1);
  295. XCTAssertFalse(realAppDelegate->_isOpenURLOptionsMethodCalled);
  296. XCTAssertEqualObjects(realAppDelegate->_backgroundSessionID, @"randomSessionID");
  297. }
  298. - (void)testProxyEmptyAppDelegate {
  299. GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
  300. OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
  301. size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
  302. Class realAppDelegateClassBefore = [realAppDelegate class];
  303. // Create the proxy.
  304. [GULAppDelegateSwizzler proxyOriginalDelegate];
  305. XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]);
  306. NSString *newClassName = NSStringFromClass([realAppDelegate class]);
  307. XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
  308. // It is no longer GULTestAppDelegate class instance.
  309. XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]);
  310. size_t sizeAfter = class_getInstanceSize([realAppDelegate class]);
  311. // Class size must stay the same.
  312. XCTAssertEqual(sizeBefore, sizeAfter);
  313. // After being proxied, it should be able to respond to the required method selector.
  314. #if TARGET_OS_IOS
  315. XCTAssertTrue([realAppDelegate
  316. respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]);
  317. #endif // TARGET_OS_IOS
  318. XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
  319. continueUserActivity:restorationHandler:)]);
  320. // Remote notifications methods should be added only by
  321. // -proxyOriginalDelegateIncludingAPNSMethods
  322. XCTAssertFalse([realAppDelegate
  323. respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
  324. XCTAssertFalse([realAppDelegate
  325. respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
  326. XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:
  327. didReceiveRemoteNotification:)]);
  328. #if TARGET_OS_IOS || TARGET_OS_TV
  329. // The implementation should not be added if there is no original implementation
  330. XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]);
  331. XCTAssertTrue([realAppDelegate
  332. respondsToSelector:@selector(application:
  333. handleEventsForBackgroundURLSession:completionHandler:)]);
  334. XCTAssertFalse([realAppDelegate
  335. respondsToSelector:@selector(application:
  336. didReceiveRemoteNotification:fetchCompletionHandler:)]);
  337. #endif // TARGET_OS_IOS || TARGET_OS_TV
  338. // Make sure that the class has changed.
  339. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore);
  340. }
  341. - (void)testProxyRemoteNotificationsMethodsEmptyAppDelegate {
  342. GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
  343. OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
  344. size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
  345. Class realAppDelegateClassBefore = [realAppDelegate class];
  346. // Create the proxy.
  347. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  348. XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]);
  349. NSString *newClassName = NSStringFromClass([realAppDelegate class]);
  350. XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
  351. // It is no longer GULTestAppDelegate class instance.
  352. XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]);
  353. size_t sizeAfter = class_getInstanceSize([realAppDelegate class]);
  354. // Class size must stay the same.
  355. XCTAssertEqual(sizeBefore, sizeAfter);
  356. // After being proxied, it should be able to respond to the required method selector.
  357. #if TARGET_OS_IOS
  358. XCTAssertTrue([realAppDelegate
  359. respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]);
  360. #endif // TARGET_OS_IOS
  361. XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
  362. continueUserActivity:restorationHandler:)]);
  363. // Remote notifications methods should be added only by
  364. // -proxyOriginalDelegateIncludingAPNSMethods
  365. XCTAssertTrue([realAppDelegate
  366. respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
  367. XCTAssertTrue([realAppDelegate
  368. respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
  369. XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
  370. didReceiveRemoteNotification:)]);
  371. #if TARGET_OS_IOS || TARGET_OS_TV
  372. // The implementation should not be added if there is no original implementation
  373. XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]);
  374. XCTAssertTrue([realAppDelegate
  375. respondsToSelector:@selector(application:
  376. handleEventsForBackgroundURLSession:completionHandler:)]);
  377. // The implementation should not be added if there is no original implementation
  378. XCTAssertFalse([realAppDelegate
  379. respondsToSelector:@selector(application:
  380. didReceiveRemoteNotification:fetchCompletionHandler:)]);
  381. #endif // TARGET_OS_IOS || TARGET_OS_TV
  382. // Make sure that the class has changed.
  383. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore);
  384. }
  385. - (void)testProxyRemoteNotificationsMethodsEmptyAppDelegateAfterInitialProxy {
  386. GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
  387. OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
  388. size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
  389. Class realAppDelegateClassBefore = [realAppDelegate class];
  390. // Create the proxy.
  391. [GULAppDelegateSwizzler proxyOriginalDelegate];
  392. XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]);
  393. NSString *newClassName = NSStringFromClass([realAppDelegate class]);
  394. XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
  395. // It is no longer GULTestAppDelegate class instance.
  396. XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]);
  397. size_t sizeAfter = class_getInstanceSize([realAppDelegate class]);
  398. // Class size must stay the same.
  399. XCTAssertEqual(sizeBefore, sizeAfter);
  400. // After being proxied, it should be able to respond to the required method selector.
  401. #if TARGET_OS_IOS
  402. XCTAssertTrue([realAppDelegate
  403. respondsToSelector:@selector(application:openURL:sourceApplication:annotation:)]);
  404. #endif // TARGET_OS_IOS
  405. XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
  406. continueUserActivity:restorationHandler:)]);
  407. // Proxy remote notifications methods
  408. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  409. XCTAssertTrue([realAppDelegate
  410. respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
  411. XCTAssertTrue([realAppDelegate
  412. respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
  413. XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
  414. didReceiveRemoteNotification:)]);
  415. #if TARGET_OS_IOS || TARGET_OS_TV
  416. // The implementation should not be added if there is no original implementation
  417. XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]);
  418. XCTAssertTrue([realAppDelegate
  419. respondsToSelector:@selector(application:
  420. handleEventsForBackgroundURLSession:completionHandler:)]);
  421. // The implementation should not be added if there is no original implementation
  422. XCTAssertFalse([realAppDelegate
  423. respondsToSelector:@selector(application:
  424. didReceiveRemoteNotification:fetchCompletionHandler:)]);
  425. #endif // TARGET_OS_IOS || TARGET_OS_TV
  426. // Make sure that the class has changed.
  427. XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore);
  428. }
  429. #if SDK_HAS_USERACTIVITY
  430. - (void)testHandleBackgroundSessionMethod {
  431. GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
  432. OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
  433. // Create the proxy.
  434. [GULAppDelegateSwizzler proxyOriginalDelegate];
  435. GULApplication *currentApplication = [GULApplication sharedApplication];
  436. NSString *sessionID = @"123";
  437. void (^nilHandler)(void) = nil;
  438. [realAppDelegate application:currentApplication
  439. handleEventsForBackgroundURLSession:sessionID
  440. completionHandler:nilHandler];
  441. // Intentionally access the ivars directly. It should be set to the session ID as the real method
  442. // is called.
  443. XCTAssertEqualObjects(realAppDelegate->_backgroundSessionID, sessionID);
  444. }
  445. #endif // SDK_HAS_USERACTIVITY
  446. /** Tests registering and unregistering invalid interceptors. */
  447. - (void)testInvalidInterceptor {
  448. #pragma clang diagnostic push
  449. #pragma clang diagnostic ignored "-Wnonnull"
  450. XCTAssertThrows([GULAppDelegateSwizzler registerAppDelegateInterceptor:nil],
  451. @"Should not register nil interceptor");
  452. #pragma clang diagnostic pop
  453. XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0);
  454. // Try to register some random object that does not conform to application delegate.
  455. NSObject *randomObject = [[NSObject alloc] init];
  456. XCTAssertThrows([GULAppDelegateSwizzler
  457. registerAppDelegateInterceptor:(id<GULApplicationDelegate>)randomObject],
  458. @"Should not register interceptor that does not conform to %@Delegate",
  459. kGULApplicationClassName);
  460. XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0);
  461. GULTestInterceptorAppDelegate *interceptorAppDelegate =
  462. [[GULTestInterceptorAppDelegate alloc] init];
  463. GULAppDelegateInterceptorID interceptorID =
  464. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptorAppDelegate];
  465. XCTAssertNotNil(interceptorID);
  466. XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
  467. // Register the same object. Should not change the number of objects.
  468. XCTAssertNotNil([GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptorAppDelegate]);
  469. XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
  470. XCTAssertThrows([GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:@""],
  471. @"Should not unregister empty interceptor ID");
  472. XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
  473. // Try to unregister an empty string. Should not remove anything.
  474. #pragma clang diagnostic push
  475. #pragma clang diagnostic ignored "-Wnonnull"
  476. XCTAssertThrows([GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:nil],
  477. @"Should not unregister nil interceptorID");
  478. XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
  479. // Try to unregister a random string. Should not remove anything.
  480. [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:@"random ID"];
  481. XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
  482. // Unregister the right one.
  483. [GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:interceptorID];
  484. XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0);
  485. }
  486. /** Tests that the description of appDelegate object doesn't change even after proxying it. */
  487. - (void)testDescription {
  488. GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
  489. OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
  490. Class classBefore = [realAppDelegate class];
  491. NSString *descriptionBefore = [realAppDelegate description];
  492. [GULAppDelegateSwizzler proxyOriginalDelegate];
  493. Class classAfter = [realAppDelegate class];
  494. NSString *descriptionAfter = [realAppDelegate description];
  495. NSString *descriptionString =
  496. [NSString stringWithFormat:@"<GULTestAppDelegate: %p>", realAppDelegate];
  497. // The description must be the same even though the class has changed.
  498. XCTAssertEqualObjects(descriptionBefore, descriptionAfter);
  499. XCTAssertNotEqualObjects(classAfter, classBefore);
  500. XCTAssertEqualObjects(descriptionAfter, descriptionString);
  501. }
  502. /** Tests that methods that are not overriden by the App Delegate Proxy still work as expected. */
  503. - (void)testNotOverriddenMethods {
  504. GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
  505. OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
  506. // Create the proxy.
  507. [GULAppDelegateSwizzler proxyOriginalDelegate];
  508. // Make sure that original class instance method still works.
  509. XCTAssertEqualObjects([realAppDelegate someArbitraryMethod], @"blabla");
  510. // Make sure that the new subclass inherits the original class method.
  511. XCTAssertEqual([[realAppDelegate class] someNumber], 890);
  512. // Make sure that the original class still works.
  513. XCTAssertEqual([GULTestAppDelegate someNumber], 890);
  514. }
  515. /** Tests that if the app delegate changes after it has been proxied, the App Delegate Swizzler
  516. * handles it correctly.
  517. */
  518. - (void)testAppDelegateInstance {
  519. // The test logic involves using KVC on the UIApplication.delegate propery. This does not really
  520. // work well with OCMPartialMock([GULApplication sharedApplication]) and triggers issue
  521. // https://github.com/erikdoe/ocmock/issues/346.
  522. // Let's stop mocking the shared application for this particular test.
  523. [self.mockSharedApplication stopMocking];
  524. self.mockSharedApplication = nil;
  525. GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
  526. [GULApplication sharedApplication].delegate = realAppDelegate;
  527. [GULAppDelegateSwizzler proxyOriginalDelegate];
  528. XCTAssertEqualObjects([GULAppDelegateSwizzler originalDelegate], realAppDelegate);
  529. GULTestInterceptorAppDelegate *anotherAppDelegate = [[GULTestInterceptorAppDelegate alloc] init];
  530. XCTAssertNotEqualObjects(realAppDelegate, anotherAppDelegate);
  531. [GULApplication sharedApplication].delegate = anotherAppDelegate;
  532. // Make sure that the new delegate is swizzled out and set correctly.
  533. XCTAssertNil([GULAppDelegateSwizzler originalDelegate]);
  534. [GULAppDelegateSwizzler proxyOriginalDelegate];
  535. // Swizzling of an updated app delegate is not supported so far.
  536. XCTAssertNil([GULAppDelegateSwizzler originalDelegate]);
  537. }
  538. #pragma mark - Tests the behaviour with interceptors
  539. #if TARGET_OS_IOS || TARGET_OS_TV
  540. /** Tests that application:openURL:options: is invoked on the interceptor if it exists. */
  541. - (void)testApplicationOpenURLOptionsIsInvokedOnInterceptors {
  542. if (@available(iOS 10, *)) {
  543. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  544. OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
  545. .andReturn(NO);
  546. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  547. OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
  548. .andReturn(NO);
  549. NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"];
  550. NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"};
  551. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  552. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  553. [GULAppDelegateSwizzler proxyOriginalDelegate];
  554. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  555. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  556. [testAppDelegate application:[GULApplication sharedApplication]
  557. openURL:testURL
  558. options:testOpenURLOptions];
  559. OCMVerifyAll(interceptor);
  560. OCMVerifyAll(interceptor2);
  561. // Check that original implementation was called with proper parameters
  562. XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
  563. XCTAssertEqual(testAppDelegate.url, testURL);
  564. }
  565. }
  566. /** Tests that the result of application:openURL:options: from all interceptors is ORed. */
  567. - (void)testResultOfApplicationOpenURLOptionsIsORed {
  568. if (@available(iOS 10, *)) {
  569. NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"];
  570. NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"};
  571. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  572. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  573. [GULAppDelegateSwizzler proxyOriginalDelegate];
  574. BOOL shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
  575. openURL:testURL
  576. options:testOpenURLOptions];
  577. // Verify that the original app delegate returns NO.
  578. XCTAssertFalse(shouldOpen);
  579. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  580. OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
  581. .andReturn(NO);
  582. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  583. shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
  584. openURL:testURL
  585. options:testOpenURLOptions];
  586. // Verify that if the only interceptor returns NO, the value is still NO.
  587. XCTAssertFalse(shouldOpen);
  588. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  589. OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
  590. .andReturn(YES);
  591. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  592. OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
  593. .andReturn(NO);
  594. shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
  595. openURL:testURL
  596. options:testOpenURLOptions];
  597. // Verify that if one of the two interceptors returns YES, the value is YES.
  598. XCTAssertTrue(shouldOpen);
  599. }
  600. }
  601. #endif // TARGET_OS_IOS || TARGET_OS_TV
  602. #if TARGET_OS_IOS
  603. /** Tests that application:openURL:sourceApplication:annotation: is invoked on the interceptors if
  604. * it exists.
  605. */
  606. - (void)testApplicationOpenURLSourceApplicationAnnotationIsInvokedOnInterceptors {
  607. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  608. OCMExpect([interceptor application:OCMOCK_ANY
  609. openURL:OCMOCK_ANY
  610. sourceApplication:OCMOCK_ANY
  611. annotation:OCMOCK_ANY])
  612. .andReturn(NO);
  613. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  614. OCMExpect([interceptor2 application:OCMOCK_ANY
  615. openURL:OCMOCK_ANY
  616. sourceApplication:OCMOCK_ANY
  617. annotation:OCMOCK_ANY])
  618. .andReturn(NO);
  619. NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"];
  620. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  621. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  622. [GULAppDelegateSwizzler proxyOriginalDelegate];
  623. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  624. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  625. NSString *sourceApplication = @"testApp";
  626. NSString *annotation = @"testAnnotation";
  627. [testAppDelegate application:[GULApplication sharedApplication]
  628. openURL:testURL
  629. sourceApplication:sourceApplication
  630. annotation:annotation];
  631. OCMVerifyAll(interceptor);
  632. OCMVerifyAll(interceptor2);
  633. // Check that original implementation was called with proper parameters
  634. XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
  635. XCTAssertEqual(testAppDelegate.url, testURL);
  636. XCTAssertEqual(testAppDelegate.openURLSourceApplication, sourceApplication);
  637. XCTAssertEqual(testAppDelegate.openURLAnnotation, annotation);
  638. }
  639. /** Tests that the result of application:openURL:sourceApplication:annotation: from all interceptors
  640. * is ORed.
  641. */
  642. - (void)testApplicationOpenURLSourceApplicationAnnotationResultIsORed {
  643. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  644. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  645. NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"];
  646. [GULAppDelegateSwizzler proxyOriginalDelegate];
  647. BOOL shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
  648. openURL:testURL
  649. sourceApplication:@"test"
  650. annotation:@"test"];
  651. // Verify that without interceptors the result is NO.
  652. XCTAssertFalse(shouldOpen);
  653. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  654. OCMExpect([interceptor application:OCMOCK_ANY
  655. openURL:OCMOCK_ANY
  656. sourceApplication:OCMOCK_ANY
  657. annotation:OCMOCK_ANY])
  658. .andReturn(NO);
  659. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  660. shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
  661. openURL:testURL
  662. sourceApplication:@"test"
  663. annotation:@"test"];
  664. // The result is still NO if the only interceptor returns NO.
  665. XCTAssertFalse(shouldOpen);
  666. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  667. OCMExpect([interceptor2 application:OCMOCK_ANY
  668. openURL:OCMOCK_ANY
  669. sourceApplication:OCMOCK_ANY
  670. annotation:OCMOCK_ANY])
  671. .andReturn(YES);
  672. OCMExpect([interceptor application:OCMOCK_ANY
  673. openURL:OCMOCK_ANY
  674. sourceApplication:OCMOCK_ANY
  675. annotation:OCMOCK_ANY])
  676. .andReturn(NO);
  677. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  678. shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
  679. openURL:testURL
  680. sourceApplication:@"test"
  681. annotation:@"test"];
  682. // The result is YES if one of the interceptors returns YES.
  683. XCTAssertTrue(shouldOpen);
  684. }
  685. #endif // TARGET_OS_IOS
  686. #if TARGET_OS_IOS || TARGET_OS_TV
  687. /** Tests that application:handleEventsForBackgroundURLSession:completionHandler: is invoked on the
  688. * interceptors if it exists.
  689. */
  690. - (void)testApplicationHandleEventsForBackgroundURLSessionIsInvokedOnInterceptors {
  691. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  692. OCMExpect([interceptor application:OCMOCK_ANY
  693. handleEventsForBackgroundURLSession:OCMOCK_ANY
  694. completionHandler:OCMOCK_ANY]);
  695. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  696. OCMExpect([interceptor2 application:OCMOCK_ANY
  697. handleEventsForBackgroundURLSession:OCMOCK_ANY
  698. completionHandler:OCMOCK_ANY]);
  699. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  700. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  701. [GULAppDelegateSwizzler proxyOriginalDelegate];
  702. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  703. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  704. NSString *backgroundSessionID = @"testBackgroundSessionID";
  705. [testAppDelegate application:[GULApplication sharedApplication]
  706. handleEventsForBackgroundURLSession:backgroundSessionID
  707. completionHandler:^{
  708. }];
  709. OCMVerifyAll(interceptor);
  710. OCMVerifyAll(interceptor2);
  711. // Check that original implementation was called with proper parameters
  712. XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
  713. XCTAssertEqual(testAppDelegate->_backgroundSessionID, backgroundSessionID);
  714. }
  715. #endif // TARGET_OS_IOS || TARGET_OS_TV
  716. /** Tests that application:continueUserActivity:restorationHandler: is invoked on the interceptors
  717. * if it exists.
  718. */
  719. - (void)testApplicationContinueUserActivityRestorationHandlerIsInvokedOnInterceptors {
  720. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  721. OCMExpect([interceptor application:OCMOCK_ANY
  722. continueUserActivity:OCMOCK_ANY
  723. restorationHandler:OCMOCK_ANY])
  724. .andReturn(NO);
  725. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  726. OCMExpect([interceptor2 application:OCMOCK_ANY
  727. continueUserActivity:OCMOCK_ANY
  728. restorationHandler:OCMOCK_ANY])
  729. .andReturn(NO);
  730. NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"];
  731. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  732. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  733. [GULAppDelegateSwizzler proxyOriginalDelegate];
  734. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  735. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  736. [testAppDelegate application:[GULApplication sharedApplication]
  737. continueUserActivity:testUserActivity
  738. restorationHandler:^(NSArray *restorableObjects){
  739. }];
  740. OCMVerifyAll(interceptor);
  741. OCMVerifyAll(interceptor2);
  742. // Check that original implementation was called with proper parameters
  743. XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
  744. XCTAssertEqual(testAppDelegate.userActivity, testUserActivity);
  745. }
  746. /** Tests that the results of application:continueUserActivity:restorationHandler: from the
  747. * interceptors are ORed.
  748. */
  749. - (void)testApplicationContinueUserActivityRestorationHandlerResultsAreORed {
  750. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  751. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  752. [GULAppDelegateSwizzler proxyOriginalDelegate];
  753. NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"];
  754. BOOL shouldContinueUserActvitiy = [testAppDelegate application:[GULApplication sharedApplication]
  755. continueUserActivity:testUserActivity
  756. restorationHandler:^(NSArray *restorableObjects){
  757. }];
  758. // Verify that it is NO when there are no interceptors.
  759. XCTAssertFalse(shouldContinueUserActvitiy);
  760. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  761. OCMExpect([interceptor application:OCMOCK_ANY
  762. continueUserActivity:OCMOCK_ANY
  763. restorationHandler:OCMOCK_ANY])
  764. .andReturn(NO);
  765. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  766. shouldContinueUserActvitiy = [testAppDelegate application:[GULApplication sharedApplication]
  767. continueUserActivity:testUserActivity
  768. restorationHandler:^(NSArray *restorableObjects){
  769. }];
  770. // Verify that it is NO when the only interceptor returns a NO.
  771. XCTAssertFalse(shouldContinueUserActvitiy);
  772. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  773. OCMExpect([interceptor2 application:OCMOCK_ANY
  774. continueUserActivity:OCMOCK_ANY
  775. restorationHandler:OCMOCK_ANY])
  776. .andReturn(YES);
  777. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  778. OCMExpect([interceptor application:OCMOCK_ANY
  779. continueUserActivity:OCMOCK_ANY
  780. restorationHandler:OCMOCK_ANY])
  781. .andReturn(NO);
  782. shouldContinueUserActvitiy = [testAppDelegate application:[GULApplication sharedApplication]
  783. continueUserActivity:testUserActivity
  784. restorationHandler:^(NSArray *restorableObjects){
  785. }];
  786. // The result is YES if one of the interceptors returns YES.
  787. XCTAssertTrue(shouldContinueUserActvitiy);
  788. }
  789. - (void)testApplicationDidRegisterForRemoteNotificationsIsInvokedOnInterceptors {
  790. NSData *deviceToken = [NSData data];
  791. GULApplication *application = [GULApplication sharedApplication];
  792. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  793. OCMExpect([interceptor application:application
  794. didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]);
  795. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  796. OCMExpect([interceptor2 application:application
  797. didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]);
  798. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  799. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  800. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  801. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  802. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  803. [testAppDelegate application:application
  804. didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
  805. OCMVerifyAll(interceptor);
  806. OCMVerifyAll(interceptor2);
  807. XCTAssertEqual(testAppDelegate.application, application);
  808. XCTAssertEqual(testAppDelegate.remoteNotificationsDeviceToken, deviceToken);
  809. }
  810. - (void)testApplicationDidFailToRegisterForRemoteNotificationsIsInvokedOnInterceptors {
  811. NSError *error = [NSError errorWithDomain:@"test" code:-1 userInfo:nil];
  812. GULApplication *application = [GULApplication sharedApplication];
  813. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  814. OCMExpect([interceptor application:application
  815. didFailToRegisterForRemoteNotificationsWithError:error]);
  816. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  817. OCMExpect([interceptor2 application:application
  818. didFailToRegisterForRemoteNotificationsWithError:error]);
  819. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  820. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  821. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  822. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  823. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  824. [testAppDelegate application:application didFailToRegisterForRemoteNotificationsWithError:error];
  825. OCMVerifyAll(interceptor);
  826. OCMVerifyAll(interceptor2);
  827. XCTAssertEqual(testAppDelegate.application, application);
  828. XCTAssertEqual(testAppDelegate.failToRegisterForRemoteNotificationsError, error);
  829. }
  830. #pragma clang diagnostic push
  831. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  832. - (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors {
  833. NSDictionary *notification = @{};
  834. GULApplication *application = [GULApplication sharedApplication];
  835. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  836. OCMExpect([interceptor application:application didReceiveRemoteNotification:notification]);
  837. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  838. OCMExpect([interceptor2 application:application didReceiveRemoteNotification:notification]);
  839. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  840. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  841. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  842. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  843. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  844. [testAppDelegate application:application didReceiveRemoteNotification:notification];
  845. OCMVerifyAll(interceptor);
  846. OCMVerifyAll(interceptor2);
  847. XCTAssertEqual(testAppDelegate.application, application);
  848. XCTAssertEqual(testAppDelegate.remoteNotification, notification);
  849. }
  850. #pragma clang diagnostic pop
  851. #if TARGET_OS_IOS || TARGET_OS_TV
  852. - (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors {
  853. NSDictionary *notification = @{};
  854. GULApplication *application = [GULApplication sharedApplication];
  855. void (^completion)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
  856. };
  857. id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
  858. OCMExpect([interceptor application:application
  859. didReceiveRemoteNotification:notification
  860. fetchCompletionHandler:completion]);
  861. id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
  862. OCMExpect([interceptor2 application:application
  863. didReceiveRemoteNotification:notification
  864. fetchCompletionHandler:completion]);
  865. GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
  866. OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
  867. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  868. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
  869. [GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
  870. [testAppDelegate application:application
  871. didReceiveRemoteNotification:notification
  872. fetchCompletionHandler:completion];
  873. OCMVerifyAll(interceptor);
  874. OCMVerifyAll(interceptor2);
  875. XCTAssertEqual(testAppDelegate.application, application);
  876. XCTAssertEqual(testAppDelegate.remoteNotification, notification);
  877. XCTAssertEqual(testAppDelegate.remoteNotificationCompletionHandler, completion);
  878. }
  879. - (void)testApplicationDidReceiveRemoteNotificationWithCompletionImplementationIsNotAdded {
  880. // The delegate without application:didReceiveRemoteNotification:fetchCompletionHandler:
  881. // implementation
  882. GULTestInterceptorAppDelegate *legacyDelegate = [[GULTestInterceptorAppDelegate alloc] init];
  883. OCMStub([self.mockSharedApplication delegate]).andReturn(legacyDelegate);
  884. XCTAssertFalse([legacyDelegate
  885. respondsToSelector:@selector(application:
  886. didReceiveRemoteNotification:fetchCompletionHandler:)]);
  887. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  888. XCTAssertFalse([legacyDelegate
  889. respondsToSelector:@selector(application:
  890. didReceiveRemoteNotification:fetchCompletionHandler:)]);
  891. }
  892. #endif // TARGET_OS_IOS || TARGET_OS_TV
  893. #pragma mark - Tests to test that Plist flag is honored
  894. /** Tests that app delegate proxy is enabled when there is no Info.plist dictionary. */
  895. - (void)testAppProxyPlistFlag_NoFlag {
  896. // No keys anywhere. If there is no key, the default should be enabled.
  897. NSDictionary *mainDictionary = nil;
  898. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  899. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  900. XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  901. [mainBundleMock stopMocking];
  902. }
  903. /** Tests that app delegate proxy is enabled when there is neither the Firebase nor the non-Firebase
  904. * Info.plist key present.
  905. */
  906. - (void)testAppProxyPlistFlag_NoAppDelegateProxyKey {
  907. // No app delegate disable key. If there is no key, the default should be enabled.
  908. NSDictionary *mainDictionary = @{@"randomKey" : @"randomValue"};
  909. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  910. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  911. XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  912. [mainBundleMock stopMocking];
  913. }
  914. /** Tests that app delegate proxy is enabled when the Firebase plist is explicitly set to YES and
  915. * the Google flag is not present. */
  916. - (void)testAppProxyPlistFlag_FirebaseEnabled {
  917. // Set proxy enabled to YES.
  918. NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(YES)};
  919. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  920. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  921. XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  922. [mainBundleMock stopMocking];
  923. }
  924. /** Tests that app delegate proxy is enabled when the Google plist is explicitly set to YES and the
  925. * Firebase flag is not present. */
  926. - (void)testAppProxyPlistFlag_GoogleEnabled {
  927. // Set proxy enabled to YES.
  928. NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @(YES)};
  929. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  930. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  931. XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  932. [mainBundleMock stopMocking];
  933. }
  934. /** Tests that the App Delegate proxy is enabled when the Firebase flag has the wrong type of value
  935. * and the Google flag is not present. */
  936. - (void)testAppProxyPlist_WrongFirebaseDisableFlagValueType {
  937. // Set proxy enabled to "NO" - a string.
  938. NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @"NO"};
  939. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  940. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  941. XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  942. [mainBundleMock stopMocking];
  943. }
  944. /** Tests that the App Delegate proxy is enabled when the Google flag has the wrong type of value
  945. * and the Firebase flag is not present. */
  946. - (void)testAppProxyPlist_WrongGoogleDisableFlagValueType {
  947. // Set proxy enabled to "NO" - a string.
  948. NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @"NO"};
  949. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  950. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  951. XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  952. [mainBundleMock stopMocking];
  953. }
  954. /** Tests that the App Delegate proxy is disabled when the Firebase flag is set to NO and the Google
  955. * flag is not present. */
  956. - (void)testAppProxyPlist_FirebaseDisableFlag {
  957. // Set proxy enabled to NO.
  958. NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)};
  959. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  960. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  961. XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  962. [mainBundleMock stopMocking];
  963. }
  964. /** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase
  965. * flag is not present. */
  966. - (void)testAppProxyPlist_GoogleDisableFlag {
  967. // Set proxy enabled to NO.
  968. NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO)};
  969. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  970. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  971. XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  972. [mainBundleMock stopMocking];
  973. }
  974. /** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase
  975. * flag is set to YES. */
  976. - (void)testAppProxyPlist_GoogleDisableFlagFirebaseEnableFlag {
  977. // Set proxy enabled to NO.
  978. NSDictionary *mainDictionary = @{
  979. kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO),
  980. kGULFirebaseAppDelegateProxyEnabledPlistKey : @(YES)
  981. };
  982. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  983. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  984. XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  985. [mainBundleMock stopMocking];
  986. }
  987. /** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase
  988. * flag is set to YES. */
  989. - (void)testAppProxyPlist_FirebaseDisableFlagGoogleEnableFlag {
  990. // Set proxy enabled to NO.
  991. NSDictionary *mainDictionary = @{
  992. kGULGoogleAppDelegateProxyEnabledPlistKey : @(YES),
  993. kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)
  994. };
  995. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  996. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  997. XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  998. [mainBundleMock stopMocking];
  999. }
  1000. /** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase
  1001. * flag is set to NO. */
  1002. - (void)testAppProxyPlist_FirebaseDisableFlagGoogleDisableFlag {
  1003. // Set proxy enabled to NO.
  1004. NSDictionary *mainDictionary = @{
  1005. kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO),
  1006. kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)
  1007. };
  1008. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  1009. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  1010. XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  1011. [mainBundleMock stopMocking];
  1012. }
  1013. /** Tests that the App Delegate is not proxied when it is disabled. */
  1014. - (void)testAppDelegateIsNotProxiedWhenDisabled {
  1015. // Set proxy enabled to NO.
  1016. NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)};
  1017. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  1018. [[[mainBundleMock stub] andReturn:mainDictionary] infoDictionary];
  1019. XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  1020. id originalAppDelegate = OCMProtocolMock(@protocol(GULApplicationDelegate));
  1021. Class originalAppDelegateClass = [originalAppDelegate class];
  1022. XCTAssertNotNil(originalAppDelegate);
  1023. OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate);
  1024. [GULAppDelegateSwizzler proxyOriginalDelegate];
  1025. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  1026. XCTAssertEqualObjects([originalAppDelegate class], originalAppDelegateClass);
  1027. [mainBundleMock stopMocking];
  1028. }
  1029. // TODO(tejasd): There is some weirdness that happens (at least when running this locally on Xcode)
  1030. // where the actual app delegate is nilled out in one of these tests, causing the tests to fail.
  1031. // Disabling this test seems to fix the problem.
  1032. /** Tests that the App Delegate is proxied when it is enabled. */
  1033. - (void)testAppDelegateIsProxiedWhenEnabled {
  1034. // App Delegate Proxying is enabled by default.
  1035. XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  1036. id originalAppDelegate = [[GULTestAppDelegate alloc] init];
  1037. Class originalAppDelegateClass = [originalAppDelegate class];
  1038. XCTAssertNotNil(originalAppDelegate);
  1039. OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate);
  1040. [GULAppDelegateSwizzler proxyOriginalDelegate];
  1041. XCTAssertNotEqualObjects([originalAppDelegate class], originalAppDelegateClass);
  1042. }
  1043. - (void)testAppDelegateIsProxiedIncludingAPNSMethodsWhenEnabled {
  1044. // App Delegate Proxying is enabled by default.
  1045. XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
  1046. id originalAppDelegate = [[GULTestAppDelegate alloc] init];
  1047. Class originalAppDelegateClass = [originalAppDelegate class];
  1048. XCTAssertNotNil(originalAppDelegate);
  1049. OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate);
  1050. [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
  1051. XCTAssertNotEqualObjects([originalAppDelegate class], originalAppDelegateClass);
  1052. }
  1053. @end