mirror of
https://github.com/google/GoogleUtilities
synced 2026-11-10 22:35:54 +05:45
1252 lines
54 KiB
Objective-C
1252 lines
54 KiB
Objective-C
// Copyright 2022 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#import "GoogleUtilitiesMulticastAppDelegate/Sources/Public/GoogleUtilities/GULMulticastAppDelegate.h"
|
|
|
|
#import <UIKit/UIKit.h>
|
|
#import <XCTest/XCTest.h>
|
|
#import <objc/runtime.h>
|
|
#import "OCMock.h"
|
|
|
|
#if (defined(__IPHONE_9_0) && (__IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0))
|
|
#define SDK_HAS_USERACTIVITY 1
|
|
#endif
|
|
|
|
/** Plist key that allows Firebase developers to disable App Delegate Proxying. Source of truth is
|
|
* the GULAppDelegateSwizzler class.
|
|
*/
|
|
static NSString *const kGULFirebaseAppDelegateProxyEnabledPlistKey =
|
|
@"FirebaseAppDelegateProxyEnabled";
|
|
|
|
/** Plist key that allows non-Firebase developers to disable App Delegate Proxying. Source of truth
|
|
* is the GULAppDelegateSwizzler class.
|
|
*/
|
|
static NSString *const kGULGoogleAppDelegateProxyEnabledPlistKey =
|
|
@"GoogleUtilitiesAppDelegateProxyEnabled";
|
|
|
|
#pragma mark - GULTestAppDelegate
|
|
|
|
/** This class conforms to the application delegate protocol and is there to be able to test the
|
|
* App Delegate Swizzler's behavior.
|
|
*/
|
|
@interface GULTestAppDelegate : NSObject
|
|
, UIApplicationDelegate {
|
|
@public // Because we want to access the ivars from outside the class like obj->ivar for testing.
|
|
/** YES if the application:openURL:options: was called on an instance, NO otherwise. */
|
|
BOOL _isOpenURLOptionsMethodCalled;
|
|
|
|
/** Contains the backgroundSessionID that was passed to the
|
|
* application:handleEventsForBackgroundURLSession:completionHandler: method.
|
|
*/
|
|
NSString *_backgroundSessionID;
|
|
|
|
/** YES if init was called. Used for memory layout testing after isa swizzling. */
|
|
BOOL _isInitialized;
|
|
|
|
/** An arbitrary number. Used for memory layout testing after isa swizzling. */
|
|
int _arbitraryNumber;
|
|
}
|
|
|
|
/** A URL property that is set by the app delegate methods, which is then used to verify if the app
|
|
* delegate methods were properly called.
|
|
*/
|
|
@property(nonatomic, strong) NSURL *url;
|
|
@property(nonatomic, strong) NSDictionary<NSString *, id> *openURLOptions;
|
|
@property(nonatomic, strong) NSString *openURLSourceApplication;
|
|
|
|
@property(nonatomic, strong) NSUserActivity *userActivity;
|
|
|
|
@property(nonatomic, strong) NSData *remoteNotificationsDeviceToken;
|
|
@property(nonatomic, strong) NSError *failToRegisterForRemoteNotificationsError;
|
|
@property(nonatomic, strong) NSDictionary *remoteNotification;
|
|
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
@property(nonatomic, copy) void (^remoteNotificationCompletionHandler)(UIBackgroundFetchResult);
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
/**
|
|
* The application is set each time a GULApplicationDelegate method is called
|
|
*/
|
|
@property(nonatomic, weak) GULApplication *application;
|
|
|
|
@end
|
|
|
|
@implementation GULTestAppDelegate
|
|
|
|
// TODO: The static BOOLs below being accurate is dependent on the runtime loading
|
|
// GULTestAppDelegate before GULAppDelegateSwizzlerTest. It works, but it might be a good idea to
|
|
// figure a way to make this more deterministic.
|
|
|
|
/** YES if GULTestAppDelegate responds to application:openURL:options:, NO otherwise. */
|
|
static BOOL gRespondsToOpenURLHandler_iOS9;
|
|
|
|
/** YES if GULTestAppDelegate responds to application:continueUserActivity:restorationHandler:, NO
|
|
* otherwise.
|
|
*/
|
|
static BOOL gRespondsToContinueUserActivity;
|
|
|
|
/** YES if GULTestAppDelegate responds to
|
|
* application:handleEventsForBackgroundURLSession:completionHandler:, NO otherwise.
|
|
*/
|
|
static BOOL gRespondsToHandleBackgroundSession;
|
|
|
|
+ (void)load {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
|
|
gRespondsToOpenURLHandler_iOS9 =
|
|
[self instancesRespondToSelector:@selector(application:openURL:options:)];
|
|
gRespondsToHandleBackgroundSession =
|
|
[self instancesRespondToSelector:@selector(application:
|
|
handleEventsForBackgroundURLSession:completionHandler:)];
|
|
gRespondsToContinueUserActivity = [self
|
|
instancesRespondToSelector:@selector(application:continueUserActivity:restorationHandler:)];
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (self) {
|
|
_isOpenURLOptionsMethodCalled = NO;
|
|
_isInitialized = YES;
|
|
_arbitraryNumber = 123456789;
|
|
_backgroundSessionID = @"randomSessionID";
|
|
_url = nil;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)application:(GULApplication *)app
|
|
openURL:(NSURL *)url
|
|
options:(NSDictionary<NSString *, id> *)options {
|
|
self.application = app;
|
|
self.url = url;
|
|
self.openURLOptions = options;
|
|
_isOpenURLOptionsMethodCalled = YES;
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)application:(GULApplication *)application
|
|
continueUserActivity:(NSUserActivity *)userActivity
|
|
restorationHandler:(void (^)(NSArray<id<GULUserActivityRestoring>> *__nullable
|
|
restorableObjects))restorationHandler {
|
|
self.application = application;
|
|
self.userActivity = userActivity;
|
|
return NO;
|
|
}
|
|
|
|
- (void)application:(GULApplication *)application
|
|
handleEventsForBackgroundURLSession:(nonnull NSString *)identifier
|
|
completionHandler:(nonnull void (^)(void))completionHandler {
|
|
self.application = application;
|
|
_backgroundSessionID = identifier;
|
|
}
|
|
|
|
- (void)application:(GULApplication *)application
|
|
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
|
self.application = application;
|
|
self.remoteNotificationsDeviceToken = deviceToken;
|
|
}
|
|
|
|
- (void)application:(GULApplication *)application
|
|
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
|
|
self.application = application;
|
|
self.failToRegisterForRemoteNotificationsError = error;
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
|
|
- (void)application:(GULApplication *)application
|
|
didReceiveRemoteNotification:(NSDictionary *)userInfo {
|
|
self.application = application;
|
|
self.remoteNotification = userInfo;
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
- (void)application:(GULApplication *)application
|
|
didReceiveRemoteNotification:(NSDictionary *)userInfo
|
|
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
|
self.application = application;
|
|
self.remoteNotification = userInfo;
|
|
self.remoteNotificationCompletionHandler = completionHandler;
|
|
}
|
|
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
// These are methods to test whether changing the class still maintains behavior that the app
|
|
// delegate proxy shouldn't have modified.
|
|
|
|
- (NSString *)someArbitraryMethod {
|
|
return @"blabla";
|
|
}
|
|
|
|
+ (int)someNumber {
|
|
return 890;
|
|
}
|
|
|
|
@end
|
|
|
|
@interface GULEmptyTestAppDelegate : NSObject <GULApplicationDelegate>
|
|
@end
|
|
|
|
@implementation GULEmptyTestAppDelegate
|
|
@end
|
|
|
|
#pragma mark - Interceptor class
|
|
|
|
/** This is a class used to test whether interceptors work with the App Delegate Swizzler. */
|
|
@interface GULTestInterceptorAppDelegate : NSObject <GULApplicationDelegate>
|
|
|
|
/** URL sent to application:openURL:options:. */
|
|
@property(nonatomic, copy) NSURL *URLForIOS9;
|
|
|
|
/** The NSUserActivity sent to application:continueUserActivity:restorationHandler:. */
|
|
@property(nonatomic, copy) NSUserActivity *userActivity;
|
|
|
|
@end
|
|
|
|
@implementation GULTestInterceptorAppDelegate
|
|
|
|
- (BOOL)application:(GULApplication *)app
|
|
openURL:(NSURL *)url
|
|
options:(NSDictionary<NSString *, id> *)options {
|
|
_URLForIOS9 = [url copy];
|
|
return YES;
|
|
}
|
|
|
|
#if SDK_HAS_USERACTIVITY
|
|
|
|
- (BOOL)application:(GULApplication *)application
|
|
continueUserActivity:(NSUserActivity *)userActivity
|
|
restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *__nullable
|
|
restorableObjects))restorationHandler {
|
|
_userActivity = userActivity;
|
|
return YES;
|
|
}
|
|
|
|
#endif // SDK_HAS_USERACTIVITY
|
|
|
|
@end
|
|
|
|
@interface GULAppDelegateSwizzlerTest : XCTestCase
|
|
@property(nonatomic, strong) id mockSharedApplication;
|
|
@end
|
|
|
|
@implementation GULAppDelegateSwizzlerTest
|
|
|
|
- (void)setUp {
|
|
[super setUp];
|
|
self.mockSharedApplication = OCMClassMock([GULApplication class]);
|
|
OCMStub([self.mockSharedApplication sharedApplication]).andReturn(self.mockSharedApplication);
|
|
}
|
|
|
|
- (void)tearDown {
|
|
[GULAppDelegateSwizzler clearInterceptors];
|
|
[GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken];
|
|
self.mockSharedApplication = nil;
|
|
[super tearDown];
|
|
}
|
|
|
|
- (void)testNotAppDelegateIsNotSwizzled {
|
|
NSObject *notAppDelegate = [[NSObject alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(notAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
XCTAssertEqualObjects(NSStringFromClass([notAppDelegate class]), @"NSObject");
|
|
}
|
|
|
|
/** Tests proxying an object that responds to application delegate protocol and makes sure that
|
|
* it is isa swizzled and that the object after proxying responds to the expected methods
|
|
* and doesn't have its ivars modified.
|
|
*/
|
|
- (void)testProxyAppDelegate {
|
|
GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
|
|
size_t sizeBefore = class_getInstanceSize([GULTestAppDelegate class]);
|
|
|
|
Class realAppDelegateClassBefore = [realAppDelegate class];
|
|
|
|
// Create the proxy.
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
XCTAssertTrue([realAppDelegate isKindOfClass:[GULTestAppDelegate class]]);
|
|
|
|
NSString *newClassName = NSStringFromClass([realAppDelegate class]);
|
|
XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
|
|
// It is no longer GULTestAppDelegate class instance.
|
|
XCTAssertFalse([realAppDelegate isMemberOfClass:[GULTestAppDelegate class]]);
|
|
|
|
size_t sizeAfter = class_getInstanceSize([realAppDelegate class]);
|
|
|
|
// Class size must stay the same.
|
|
XCTAssertEqual(sizeBefore, sizeAfter);
|
|
|
|
XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
|
|
continueUserActivity:restorationHandler:)]);
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
|
|
XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:)]);
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]);
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:
|
|
handleEventsForBackgroundURLSession:completionHandler:)]);
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:fetchCompletionHandler:)]);
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
// Make sure that the class has changed.
|
|
XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore);
|
|
|
|
// Make sure that the ivars are not changed in memory as the subclass is created. Directly
|
|
// accessing the ivars should not crash.
|
|
XCTAssertEqual(realAppDelegate->_arbitraryNumber, 123456789);
|
|
XCTAssertEqual(realAppDelegate->_isInitialized, 1);
|
|
XCTAssertFalse(realAppDelegate->_isOpenURLOptionsMethodCalled);
|
|
XCTAssertEqualObjects(realAppDelegate->_backgroundSessionID, @"randomSessionID");
|
|
}
|
|
|
|
- (void)testProxyEmptyAppDelegate {
|
|
GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
|
|
size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
|
|
|
|
Class realAppDelegateClassBefore = [realAppDelegate class];
|
|
|
|
// Create the proxy.
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]);
|
|
|
|
NSString *newClassName = NSStringFromClass([realAppDelegate class]);
|
|
XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
|
|
// It is no longer GULTestAppDelegate class instance.
|
|
XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]);
|
|
|
|
size_t sizeAfter = class_getInstanceSize([realAppDelegate class]);
|
|
|
|
// Class size must stay the same.
|
|
XCTAssertEqual(sizeBefore, sizeAfter);
|
|
|
|
XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
|
|
continueUserActivity:restorationHandler:)]);
|
|
// Remote notifications methods should be added only by
|
|
// -proxyOriginalDelegateIncludingAPNSMethods
|
|
XCTAssertFalse([realAppDelegate
|
|
respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
|
|
XCTAssertFalse([realAppDelegate
|
|
respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
|
|
XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:)]);
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
// The implementation should not be added if there is no original implementation
|
|
XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]);
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:
|
|
handleEventsForBackgroundURLSession:completionHandler:)]);
|
|
XCTAssertFalse([realAppDelegate
|
|
respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:fetchCompletionHandler:)]);
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
// Make sure that the class has changed.
|
|
XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore);
|
|
}
|
|
|
|
- (void)testProxyRemoteNotificationsMethodsEmptyAppDelegate {
|
|
GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
|
|
size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
|
|
|
|
Class realAppDelegateClassBefore = [realAppDelegate class];
|
|
|
|
// Create the proxy.
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
|
|
XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]);
|
|
|
|
NSString *newClassName = NSStringFromClass([realAppDelegate class]);
|
|
XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
|
|
// It is no longer GULTestAppDelegate class instance.
|
|
XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]);
|
|
|
|
size_t sizeAfter = class_getInstanceSize([realAppDelegate class]);
|
|
|
|
// Class size must stay the same.
|
|
XCTAssertEqual(sizeBefore, sizeAfter);
|
|
|
|
XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
|
|
continueUserActivity:restorationHandler:)]);
|
|
|
|
// Remote notifications methods should be added only by
|
|
// -proxyOriginalDelegateIncludingAPNSMethods
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
|
|
XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:)]);
|
|
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
// The implementation should not be added if there is no original implementation
|
|
XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]);
|
|
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:
|
|
handleEventsForBackgroundURLSession:completionHandler:)]);
|
|
|
|
// The implementation should not be added if there is no original implementation
|
|
XCTAssertFalse([realAppDelegate
|
|
respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:fetchCompletionHandler:)]);
|
|
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
// Make sure that the class has changed.
|
|
XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore);
|
|
}
|
|
|
|
- (void)testProxyRemoteNotificationsMethodsEmptyAppDelegateAfterInitialProxy {
|
|
GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
|
|
size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
|
|
|
|
Class realAppDelegateClassBefore = [realAppDelegate class];
|
|
|
|
// Create the proxy.
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
XCTAssertTrue([realAppDelegate isKindOfClass:[GULEmptyTestAppDelegate class]]);
|
|
|
|
NSString *newClassName = NSStringFromClass([realAppDelegate class]);
|
|
XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
|
|
// It is no longer GULTestAppDelegate class instance.
|
|
XCTAssertFalse([realAppDelegate isMemberOfClass:[GULEmptyTestAppDelegate class]]);
|
|
|
|
size_t sizeAfter = class_getInstanceSize([realAppDelegate class]);
|
|
|
|
// Class size must stay the same.
|
|
XCTAssertEqual(sizeBefore, sizeAfter);
|
|
|
|
XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
|
|
continueUserActivity:restorationHandler:)]);
|
|
// Proxy remote notifications methods
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:didRegisterForRemoteNotificationsWithDeviceToken:)]);
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:didFailToRegisterForRemoteNotificationsWithError:)]);
|
|
XCTAssertTrue([realAppDelegate respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:)]);
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
// The implementation should not be added if there is no original implementation
|
|
XCTAssertFalse([realAppDelegate respondsToSelector:@selector(application:openURL:options:)]);
|
|
XCTAssertTrue([realAppDelegate
|
|
respondsToSelector:@selector(application:
|
|
handleEventsForBackgroundURLSession:completionHandler:)]);
|
|
|
|
// The implementation should not be added if there is no original implementation
|
|
XCTAssertFalse([realAppDelegate
|
|
respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:fetchCompletionHandler:)]);
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
// Make sure that the class has changed.
|
|
XCTAssertNotEqualObjects([realAppDelegate class], realAppDelegateClassBefore);
|
|
}
|
|
|
|
#if SDK_HAS_USERACTIVITY
|
|
- (void)testHandleBackgroundSessionMethod {
|
|
GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
|
|
|
|
// Create the proxy.
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
GULApplication *currentApplication = [GULApplication sharedApplication];
|
|
NSString *sessionID = @"123";
|
|
void (^nilHandler)(void) = nil;
|
|
[realAppDelegate application:currentApplication
|
|
handleEventsForBackgroundURLSession:sessionID
|
|
completionHandler:nilHandler];
|
|
|
|
// Intentionally access the ivars directly. It should be set to the session ID as the real method
|
|
// is called.
|
|
XCTAssertEqualObjects(realAppDelegate->_backgroundSessionID, sessionID);
|
|
}
|
|
#endif // SDK_HAS_USERACTIVITY
|
|
|
|
/** Tests registering and unregistering invalid interceptors. */
|
|
- (void)testInvalidInterceptor {
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wnonnull"
|
|
XCTAssertThrows([GULAppDelegateSwizzler registerAppDelegateInterceptor:nil],
|
|
@"Should not register nil interceptor");
|
|
#pragma clang diagnostic pop
|
|
XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0);
|
|
|
|
// Try to register some random object that does not conform to application delegate.
|
|
NSObject *randomObject = [[NSObject alloc] init];
|
|
|
|
XCTAssertThrows([GULAppDelegateSwizzler
|
|
registerAppDelegateInterceptor:(id<GULApplicationDelegate>)randomObject],
|
|
@"Should not register interceptor that does not conform to %@Delegate",
|
|
kGULApplicationClassName);
|
|
XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0);
|
|
|
|
GULTestInterceptorAppDelegate *interceptorAppDelegate =
|
|
[[GULTestInterceptorAppDelegate alloc] init];
|
|
GULAppDelegateInterceptorID interceptorID =
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptorAppDelegate];
|
|
XCTAssertNotNil(interceptorID);
|
|
XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
|
|
|
|
// Register the same object. Should not change the number of objects.
|
|
XCTAssertNotNil([GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptorAppDelegate]);
|
|
XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
|
|
|
|
XCTAssertThrows([GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:@""],
|
|
@"Should not unregister empty interceptor ID");
|
|
XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
|
|
|
|
// Try to unregister an empty string. Should not remove anything.
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wnonnull"
|
|
XCTAssertThrows([GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:nil],
|
|
@"Should not unregister nil interceptorID");
|
|
XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
|
|
|
|
// Try to unregister a random string. Should not remove anything.
|
|
[GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:@"random ID"];
|
|
XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 1);
|
|
|
|
// Unregister the right one.
|
|
[GULAppDelegateSwizzler unregisterAppDelegateInterceptorWithID:interceptorID];
|
|
XCTAssertEqual([GULAppDelegateSwizzler interceptors].count, 0);
|
|
}
|
|
|
|
/** Tests that the description of appDelegate object doesn't change even after proxying it. */
|
|
- (void)testDescription {
|
|
GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
|
|
Class classBefore = [realAppDelegate class];
|
|
NSString *descriptionBefore = [realAppDelegate description];
|
|
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
Class classAfter = [realAppDelegate class];
|
|
NSString *descriptionAfter = [realAppDelegate description];
|
|
|
|
NSString *descriptionString =
|
|
[NSString stringWithFormat:@"<GULTestAppDelegate: %p>", realAppDelegate];
|
|
|
|
// The description must be the same even though the class has changed.
|
|
XCTAssertEqualObjects(descriptionBefore, descriptionAfter);
|
|
XCTAssertNotEqualObjects(classAfter, classBefore);
|
|
XCTAssertEqualObjects(descriptionAfter, descriptionString);
|
|
}
|
|
|
|
/** Tests that methods that are not overridden by the App Delegate Proxy still work as expected. */
|
|
- (void)testNotOverriddenMethods {
|
|
GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
|
|
|
|
// Create the proxy.
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
// Make sure that original class instance method still works.
|
|
XCTAssertEqualObjects([realAppDelegate someArbitraryMethod], @"blabla");
|
|
|
|
// Make sure that the new subclass inherits the original class method.
|
|
XCTAssertEqual([[realAppDelegate class] someNumber], 890);
|
|
|
|
// Make sure that the original class still works.
|
|
XCTAssertEqual([GULTestAppDelegate someNumber], 890);
|
|
}
|
|
|
|
#if !SWIFT_PACKAGE
|
|
// TODO: Investigate why this test fails in Swift PM builds.
|
|
|
|
/** Tests that if the app delegate changes after it has been proxied, the App Delegate Swizzler
|
|
* handles it correctly.
|
|
*/
|
|
- (void)testAppDelegateInstance {
|
|
// The test logic involves using KVC on the UIApplication.delegate property. This does not really
|
|
// work well with OCMPartialMock([GULApplication sharedApplication]) and triggers issue
|
|
// https://github.com/erikdoe/ocmock/issues/346.
|
|
// Let's stop mocking the shared application for this particular test.
|
|
[self.mockSharedApplication stopMocking];
|
|
self.mockSharedApplication = nil;
|
|
|
|
GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
|
|
[GULApplication sharedApplication].delegate = realAppDelegate;
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
XCTAssertEqualObjects([GULAppDelegateSwizzler originalDelegate], realAppDelegate);
|
|
|
|
GULTestInterceptorAppDelegate *anotherAppDelegate = [[GULTestInterceptorAppDelegate alloc] init];
|
|
XCTAssertNotEqualObjects(realAppDelegate, anotherAppDelegate);
|
|
|
|
[GULApplication sharedApplication].delegate = anotherAppDelegate;
|
|
// Make sure that the new delegate is swizzled out and set correctly.
|
|
XCTAssertNil([GULAppDelegateSwizzler originalDelegate]);
|
|
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
// Swizzling of an updated app delegate is not supported so far.
|
|
XCTAssertNil([GULAppDelegateSwizzler originalDelegate]);
|
|
}
|
|
#endif
|
|
|
|
#pragma mark - Tests the behaviour with interceptors
|
|
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
/** Tests that application:openURL:options: is invoked on the interceptor if it exists. */
|
|
- (void)testApplicationOpenURLOptionsIsInvokedOnInterceptors {
|
|
if (@available(iOS 10, *)) {
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
|
|
.andReturn(NO);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
|
|
.andReturn(NO);
|
|
|
|
NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"];
|
|
NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"};
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
[testAppDelegate application:[GULApplication sharedApplication]
|
|
openURL:testURL
|
|
options:testOpenURLOptions];
|
|
OCMVerifyAll(interceptor);
|
|
OCMVerifyAll(interceptor2);
|
|
|
|
// Check that original implementation was called with proper parameters
|
|
XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
|
|
XCTAssertEqual(testAppDelegate.url, testURL);
|
|
}
|
|
}
|
|
|
|
/** Tests that the result of application:openURL:options: from all interceptors is ORed. */
|
|
- (void)testResultOfApplicationOpenURLOptionsIsORed {
|
|
if (@available(iOS 10, *)) {
|
|
NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"];
|
|
NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"};
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
BOOL shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
|
|
openURL:testURL
|
|
options:testOpenURLOptions];
|
|
// Verify that the original app delegate returns NO.
|
|
XCTAssertFalse(shouldOpen);
|
|
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
|
|
.andReturn(NO);
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
|
|
openURL:testURL
|
|
options:testOpenURLOptions];
|
|
// Verify that if the only interceptor returns NO, the value is still NO.
|
|
XCTAssertFalse(shouldOpen);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
|
|
.andReturn(YES);
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
OCMExpect([interceptor application:OCMOCK_ANY openURL:OCMOCK_ANY options:OCMOCK_ANY])
|
|
.andReturn(NO);
|
|
shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
|
|
openURL:testURL
|
|
options:testOpenURLOptions];
|
|
// Verify that if one of the two interceptors returns YES, the value is YES.
|
|
XCTAssertTrue(shouldOpen);
|
|
}
|
|
}
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
#if TARGET_OS_IOS || TARGET_OS_TV
|
|
/** Tests that application:handleEventsForBackgroundURLSession:completionHandler: is invoked on the
|
|
* interceptors if it exists.
|
|
*/
|
|
- (void)testApplicationHandleEventsForBackgroundURLSessionIsInvokedOnInterceptors {
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:OCMOCK_ANY
|
|
handleEventsForBackgroundURLSession:OCMOCK_ANY
|
|
completionHandler:OCMOCK_ANY]);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:OCMOCK_ANY
|
|
handleEventsForBackgroundURLSession:OCMOCK_ANY
|
|
completionHandler:OCMOCK_ANY]);
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
NSString *backgroundSessionID = @"testBackgroundSessionID";
|
|
[testAppDelegate application:[GULApplication sharedApplication]
|
|
handleEventsForBackgroundURLSession:backgroundSessionID
|
|
completionHandler:^{
|
|
}];
|
|
|
|
OCMVerifyAll(interceptor);
|
|
OCMVerifyAll(interceptor2);
|
|
|
|
// Check that original implementation was called with proper parameters
|
|
XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
|
|
XCTAssertEqual(testAppDelegate->_backgroundSessionID, backgroundSessionID);
|
|
}
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
/** Tests that application:continueUserActivity:restorationHandler: is invoked on the interceptors
|
|
* if it exists.
|
|
*/
|
|
- (void)testApplicationContinueUserActivityRestorationHandlerIsInvokedOnInterceptors {
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:OCMOCK_ANY
|
|
continueUserActivity:OCMOCK_ANY
|
|
restorationHandler:OCMOCK_ANY])
|
|
.andReturn(NO);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:OCMOCK_ANY
|
|
continueUserActivity:OCMOCK_ANY
|
|
restorationHandler:OCMOCK_ANY])
|
|
.andReturn(NO);
|
|
|
|
NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"];
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
[testAppDelegate application:[GULApplication sharedApplication]
|
|
continueUserActivity:testUserActivity
|
|
restorationHandler:^(NSArray *restorableObjects){
|
|
}];
|
|
OCMVerifyAll(interceptor);
|
|
OCMVerifyAll(interceptor2);
|
|
|
|
// Check that original implementation was called with proper parameters
|
|
XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
|
|
XCTAssertEqual(testAppDelegate.userActivity, testUserActivity);
|
|
}
|
|
|
|
/** Tests that the results of application:continueUserActivity:restorationHandler: from the
|
|
* interceptors are ORed.
|
|
*/
|
|
- (void)testApplicationContinueUserActivityRestorationHandlerResultsAreORed {
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"];
|
|
|
|
BOOL shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication]
|
|
continueUserActivity:testUserActivity
|
|
restorationHandler:^(NSArray *restorableObjects){
|
|
}];
|
|
// Verify that it is NO when there are no interceptors.
|
|
XCTAssertFalse(shouldContinueUserActivity);
|
|
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:OCMOCK_ANY
|
|
continueUserActivity:OCMOCK_ANY
|
|
restorationHandler:OCMOCK_ANY])
|
|
.andReturn(NO);
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication]
|
|
continueUserActivity:testUserActivity
|
|
restorationHandler:^(NSArray *restorableObjects){
|
|
}];
|
|
// Verify that it is NO when the only interceptor returns a NO.
|
|
XCTAssertFalse(shouldContinueUserActivity);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:OCMOCK_ANY
|
|
continueUserActivity:OCMOCK_ANY
|
|
restorationHandler:OCMOCK_ANY])
|
|
.andReturn(YES);
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
OCMExpect([interceptor application:OCMOCK_ANY
|
|
continueUserActivity:OCMOCK_ANY
|
|
restorationHandler:OCMOCK_ANY])
|
|
.andReturn(NO);
|
|
shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication]
|
|
continueUserActivity:testUserActivity
|
|
restorationHandler:^(NSArray *restorableObjects){
|
|
}];
|
|
|
|
// The result is YES if one of the interceptors returns YES.
|
|
XCTAssertTrue(shouldContinueUserActivity);
|
|
}
|
|
|
|
- (void)testApplicationDidRegisterForRemoteNotificationsIsInvokedOnInterceptors {
|
|
NSData *deviceToken = [NSData data];
|
|
GULApplication *application = [GULApplication sharedApplication];
|
|
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:application
|
|
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:application
|
|
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]);
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
[testAppDelegate application:application
|
|
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
|
|
OCMVerifyAll(interceptor);
|
|
OCMVerifyAll(interceptor2);
|
|
|
|
XCTAssertEqual(testAppDelegate.application, application);
|
|
XCTAssertEqual(testAppDelegate.remoteNotificationsDeviceToken, deviceToken);
|
|
}
|
|
|
|
- (void)testApplicationDidFailToRegisterForRemoteNotificationsIsInvokedOnInterceptors {
|
|
NSError *error = [NSError errorWithDomain:@"test" code:-1 userInfo:nil];
|
|
GULApplication *application = [GULApplication sharedApplication];
|
|
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:application
|
|
didFailToRegisterForRemoteNotificationsWithError:error]);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:application
|
|
didFailToRegisterForRemoteNotificationsWithError:error]);
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
[testAppDelegate application:application didFailToRegisterForRemoteNotificationsWithError:error];
|
|
OCMVerifyAll(interceptor);
|
|
OCMVerifyAll(interceptor2);
|
|
|
|
XCTAssertEqual(testAppDelegate.application, application);
|
|
XCTAssertEqual(testAppDelegate.failToRegisterForRemoteNotificationsError, error);
|
|
}
|
|
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
- (void)testApplicationDidReceiveRemoteNotificationIsInvokedOnInterceptors {
|
|
NSDictionary *notification = @{};
|
|
GULApplication *application = [GULApplication sharedApplication];
|
|
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:application didReceiveRemoteNotification:notification]);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:application didReceiveRemoteNotification:notification]);
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
[testAppDelegate application:application didReceiveRemoteNotification:notification];
|
|
OCMVerifyAll(interceptor);
|
|
OCMVerifyAll(interceptor2);
|
|
|
|
XCTAssertEqual(testAppDelegate.application, application);
|
|
XCTAssertEqual(testAppDelegate.remoteNotification, notification);
|
|
}
|
|
#pragma clang diagnostic pop
|
|
|
|
#if (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST
|
|
- (void)testApplicationDidReceiveRemoteNotificationWithCompletionIsInvokedOnInterceptors {
|
|
NSDictionary *notification = @{};
|
|
GULApplication *application = [GULApplication sharedApplication];
|
|
void (^completion)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
|
|
};
|
|
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:application
|
|
didReceiveRemoteNotification:notification
|
|
fetchCompletionHandler:[OCMArg isNotNil]]);
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:application
|
|
didReceiveRemoteNotification:notification
|
|
fetchCompletionHandler:[OCMArg isNotNil]]);
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
[testAppDelegate application:application
|
|
didReceiveRemoteNotification:notification
|
|
fetchCompletionHandler:completion];
|
|
OCMVerifyAll(interceptor);
|
|
OCMVerifyAll(interceptor2);
|
|
|
|
XCTAssertEqual(testAppDelegate.application, application);
|
|
XCTAssertEqual(testAppDelegate.remoteNotification, notification);
|
|
}
|
|
|
|
- (void)verifyCompletionCalledForObserverResult:(UIBackgroundFetchResult)observerResult1
|
|
anotherObserverResult:(UIBackgroundFetchResult)observerResult2
|
|
swizzledResult:(UIBackgroundFetchResult)swizzledResult
|
|
expectedResult:(UIBackgroundFetchResult)expectedResult {
|
|
NSDictionary *notification = @{};
|
|
GULApplication *application = [GULApplication sharedApplication];
|
|
|
|
XCTestExpectation *completionExpectation =
|
|
[[XCTestExpectation alloc] initWithDescription:@"Completion called once"];
|
|
|
|
void (^completion)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
|
|
XCTAssertEqual(result, expectedResult);
|
|
[completionExpectation fulfill];
|
|
};
|
|
|
|
void (^onDidReceiveRemoteNotification1)(NSInvocation *invocation) = ^(NSInvocation *invocation) {
|
|
void __unsafe_unretained (^localCompletionHandler)(UIBackgroundFetchResult) = nil;
|
|
[invocation getArgument:(void *)(&localCompletionHandler) atIndex:4];
|
|
XCTAssertNotNil(localCompletionHandler);
|
|
localCompletionHandler(observerResult1);
|
|
};
|
|
|
|
id interceptor = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor application:application
|
|
didReceiveRemoteNotification:notification
|
|
fetchCompletionHandler:[OCMArg isNotNil]])
|
|
.andDo(onDidReceiveRemoteNotification1);
|
|
|
|
void (^onDidReceiveRemoteNotification2)(NSInvocation *invocation) = ^(NSInvocation *invocation) {
|
|
void __unsafe_unretained (^localCompletionHandler)(UIBackgroundFetchResult) = nil;
|
|
[invocation getArgument:(void *)(&localCompletionHandler) atIndex:4];
|
|
XCTAssertNotNil(localCompletionHandler);
|
|
localCompletionHandler(observerResult2);
|
|
};
|
|
|
|
id interceptor2 = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
OCMExpect([interceptor2 application:application
|
|
didReceiveRemoteNotification:notification
|
|
fetchCompletionHandler:[OCMArg isNotNil]])
|
|
.andDo(onDidReceiveRemoteNotification2);
|
|
|
|
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
|
|
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
|
|
|
|
[testAppDelegate application:application
|
|
didReceiveRemoteNotification:notification
|
|
fetchCompletionHandler:completion];
|
|
testAppDelegate.remoteNotificationCompletionHandler(swizzledResult);
|
|
OCMVerifyAll(interceptor);
|
|
OCMVerifyAll(interceptor2);
|
|
[self waitForExpectations:@[ completionExpectation ] timeout:0.1];
|
|
}
|
|
|
|
- (void)testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce {
|
|
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNoData
|
|
anotherObserverResult:UIBackgroundFetchResultNoData
|
|
swizzledResult:UIBackgroundFetchResultNoData
|
|
expectedResult:UIBackgroundFetchResultNoData];
|
|
}
|
|
|
|
- (void)
|
|
testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleFailedState {
|
|
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultFailed
|
|
anotherObserverResult:UIBackgroundFetchResultFailed
|
|
swizzledResult:UIBackgroundFetchResultFailed
|
|
expectedResult:UIBackgroundFetchResultFailed];
|
|
}
|
|
|
|
- (void)testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_NoData {
|
|
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNoData
|
|
anotherObserverResult:UIBackgroundFetchResultFailed
|
|
swizzledResult:UIBackgroundFetchResultFailed
|
|
expectedResult:UIBackgroundFetchResultNoData];
|
|
}
|
|
- (void)
|
|
testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersFailed {
|
|
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData
|
|
anotherObserverResult:UIBackgroundFetchResultFailed
|
|
swizzledResult:UIBackgroundFetchResultFailed
|
|
expectedResult:UIBackgroundFetchResultNewData];
|
|
}
|
|
|
|
- (void)
|
|
testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersNoData {
|
|
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData
|
|
anotherObserverResult:UIBackgroundFetchResultNoData
|
|
swizzledResult:UIBackgroundFetchResultNoData
|
|
expectedResult:UIBackgroundFetchResultNewData];
|
|
}
|
|
|
|
- (void)
|
|
testApplicationDidReceiveRemoteNotificationWithCompletionCompletionIsCalledOnce_HandleNewDataState_OthersNoDataFailed {
|
|
[self verifyCompletionCalledForObserverResult:UIBackgroundFetchResultNewData
|
|
anotherObserverResult:UIBackgroundFetchResultNoData
|
|
swizzledResult:UIBackgroundFetchResultFailed
|
|
expectedResult:UIBackgroundFetchResultNewData];
|
|
}
|
|
|
|
- (void)testApplicationDidReceiveRemoteNotificationWithCompletionImplementationIsNotAdded {
|
|
// The delegate without application:didReceiveRemoteNotification:fetchCompletionHandler:
|
|
// implementation
|
|
GULTestInterceptorAppDelegate *legacyDelegate = [[GULTestInterceptorAppDelegate alloc] init];
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(legacyDelegate);
|
|
|
|
XCTAssertFalse([legacyDelegate
|
|
respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:fetchCompletionHandler:)]);
|
|
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
|
|
XCTAssertFalse([legacyDelegate
|
|
respondsToSelector:@selector(application:
|
|
didReceiveRemoteNotification:fetchCompletionHandler:)]);
|
|
}
|
|
#endif // TARGET_OS_IOS || TARGET_OS_TV
|
|
|
|
#pragma mark - Tests to test that Plist flag is honored
|
|
|
|
/** Tests that app delegate proxy is enabled when there is no Info.plist dictionary. */
|
|
- (void)testAppProxyPlistFlag_NoFlag {
|
|
// No keys anywhere. If there is no key, the default should be enabled.
|
|
NSDictionary *mainDictionary = nil;
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that app delegate proxy is enabled when there is neither the Firebase nor the non-Firebase
|
|
* Info.plist key present.
|
|
*/
|
|
- (void)testAppProxyPlistFlag_NoAppDelegateProxyKey {
|
|
// No app delegate disable key. If there is no key, the default should be enabled.
|
|
NSDictionary *mainDictionary = @{@"randomKey" : @"randomValue"};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that app delegate proxy is enabled when the Firebase plist is explicitly set to YES and
|
|
* the Google flag is not present. */
|
|
- (void)testAppProxyPlistFlag_FirebaseEnabled {
|
|
// Set proxy enabled to YES.
|
|
NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(YES)};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that app delegate proxy is enabled when the Google plist is explicitly set to YES and the
|
|
* Firebase flag is not present. */
|
|
- (void)testAppProxyPlistFlag_GoogleEnabled {
|
|
// Set proxy enabled to YES.
|
|
NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @(YES)};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that the App Delegate proxy is enabled when the Firebase flag has the wrong type of value
|
|
* and the Google flag is not present. */
|
|
- (void)testAppProxyPlist_WrongFirebaseDisableFlagValueType {
|
|
// Set proxy enabled to "NO" - a string.
|
|
NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @"NO"};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that the App Delegate proxy is enabled when the Google flag has the wrong type of value
|
|
* and the Firebase flag is not present. */
|
|
- (void)testAppProxyPlist_WrongGoogleDisableFlagValueType {
|
|
// Set proxy enabled to "NO" - a string.
|
|
NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @"NO"};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that the App Delegate proxy is disabled when the Firebase flag is set to NO and the Google
|
|
* flag is not present. */
|
|
- (void)testAppProxyPlist_FirebaseDisableFlag {
|
|
// Set proxy enabled to NO.
|
|
NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase
|
|
* flag is not present. */
|
|
- (void)testAppProxyPlist_GoogleDisableFlag {
|
|
// Set proxy enabled to NO.
|
|
NSDictionary *mainDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO)};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase
|
|
* flag is set to YES. */
|
|
- (void)testAppProxyPlist_GoogleDisableFlagFirebaseEnableFlag {
|
|
// Set proxy enabled to NO.
|
|
NSDictionary *mainDictionary = @{
|
|
kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO),
|
|
kGULFirebaseAppDelegateProxyEnabledPlistKey : @(YES)
|
|
};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase
|
|
* flag is set to YES. */
|
|
- (void)testAppProxyPlist_FirebaseDisableFlagGoogleEnableFlag {
|
|
// Set proxy enabled to NO.
|
|
NSDictionary *mainDictionary = @{
|
|
kGULGoogleAppDelegateProxyEnabledPlistKey : @(YES),
|
|
kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)
|
|
};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that the App Delegate proxy is disabled when the Google flag is set to NO and the Firebase
|
|
* flag is set to NO. */
|
|
- (void)testAppProxyPlist_FirebaseDisableFlagGoogleDisableFlag {
|
|
// Set proxy enabled to NO.
|
|
NSDictionary *mainDictionary = @{
|
|
kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO),
|
|
kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)
|
|
};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
|
|
|
|
XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
/** Tests that the App Delegate is not proxied when it is disabled. */
|
|
- (void)testAppDelegateIsNotProxiedWhenDisabled {
|
|
// Set proxy enabled to NO.
|
|
NSDictionary *mainDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)};
|
|
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
|
|
[[[mainBundleMock stub] andReturn:mainDictionary] infoDictionary];
|
|
XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
|
|
id originalAppDelegate = OCMProtocolMock(@protocol(GULApplicationDelegate));
|
|
Class originalAppDelegateClass = [originalAppDelegate class];
|
|
XCTAssertNotNil(originalAppDelegate);
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate);
|
|
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
XCTAssertEqualObjects([originalAppDelegate class], originalAppDelegateClass);
|
|
|
|
[mainBundleMock stopMocking];
|
|
}
|
|
|
|
// TODO(tejasd): There is some weirdness that happens (at least when running this locally on Xcode)
|
|
// where the actual app delegate is nilled out in one of these tests, causing the tests to fail.
|
|
// Disabling this test seems to fix the problem.
|
|
|
|
/** Tests that the App Delegate is proxied when it is enabled. */
|
|
- (void)testAppDelegateIsProxiedWhenEnabled {
|
|
// App Delegate Proxying is enabled by default.
|
|
XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
|
|
id originalAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
Class originalAppDelegateClass = [originalAppDelegate class];
|
|
XCTAssertNotNil(originalAppDelegate);
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate);
|
|
|
|
[GULAppDelegateSwizzler proxyOriginalDelegate];
|
|
XCTAssertNotEqualObjects([originalAppDelegate class], originalAppDelegateClass);
|
|
}
|
|
|
|
- (void)testAppDelegateIsProxiedIncludingAPNSMethodsWhenEnabled {
|
|
// App Delegate Proxying is enabled by default.
|
|
XCTAssertTrue([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
|
|
|
|
id originalAppDelegate = [[GULTestAppDelegate alloc] init];
|
|
Class originalAppDelegateClass = [originalAppDelegate class];
|
|
XCTAssertNotNil(originalAppDelegate);
|
|
OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate);
|
|
|
|
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
|
|
XCTAssertNotEqualObjects([originalAppDelegate class], originalAppDelegateClass);
|
|
}
|
|
|
|
@end
|