infra: remove OCMock dependency (#243)

* test: migrate away from OCMock

Replace dynamic OCMock mocks with handwritten fake classes across the test suite to improve Swift interoperability, address concurrency issues, and reduce test brittleness. Removed OCMock from podspec and SPM dependencies.

* gemfile

* Remove other files and deps

* Apply suggestion from @ncooke3

* Apply suggestion from @ncooke3
This commit is contained in:
Nick Cooke authored and GitHub committed 2026-08-04 14:49:15 -04:00
1 parent 9f183ae842
commit e4035cf5d3
13 files changed
+601 -515

No files matched your search

+1 -1
View File
@@ -1 +1 @@
ruby-2.7
3.4.1
-1
View File
@@ -152,7 +152,6 @@ other Google CocoaPods. They're not intended for direct public usage.
'GoogleUtilities/Tests/Unit/Environment/**/*.[mh]'
]
unit_tests.requires_app_host = true
unit_tests.dependency 'OCMock'
end
s.test_spec 'unit-swift' do |unit_tests_swift|
@@ -27,6 +27,16 @@
@implementation GULAppEnvironmentUtil
static NSProcessInfo *gProcessInfo = nil;
+ (NSProcessInfo *)processInfo {
return gProcessInfo ?: [NSProcessInfo processInfo];
}
+ (void)setProcessInfoForTest:(NSProcessInfo *)processInfo {
gProcessInfo = processInfo;
}
/// A key for the Info.plist to enable or disable checking if the App Store is running in a sandbox.
/// This will affect your data integrity when using Firebase Analytics, as it will disable some
/// necessary checks.
@@ -197,7 +207,7 @@ static BOOL HasEmbeddedMobileProvision(void) {
return [UIDevice currentDevice].systemVersion;
#elif TARGET_OS_OSX || TARGET_OS_TV || TARGET_OS_WATCH || TARGET_OS_VISION
// Assemble the systemVersion, excluding the patch version if it's 0.
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
NSOperatingSystemVersion osVersion = [self processInfo].operatingSystemVersion;
NSMutableString *versionString = [[NSMutableString alloc]
initWithFormat:@"%ld.%ld", (long)osVersion.majorVersion, (long)osVersion.minorVersion];
if (osVersion.patchVersion != 0) {
@@ -246,8 +256,8 @@ static BOOL HasEmbeddedMobileProvision(void) {
#elif TARGET_OS_IOS
if (@available(iOS 14.0, *)) {
// Early iOS 14 betas do not include isiOSAppOnMac (#6969)
applePlatform = ([[NSProcessInfo processInfo] respondsToSelector:@selector(isiOSAppOnMac)] &&
[NSProcessInfo processInfo].isiOSAppOnMac)
applePlatform = ([[self processInfo] respondsToSelector:@selector(isiOSAppOnMac)] &&
[self processInfo].isiOSAppOnMac)
? @"ios_on_mac"
: @"ios";
} else {
@@ -13,7 +13,6 @@
// limitations under the License.
#import <Foundation/Foundation.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#if __has_include(<UIKit/UIKit.h>)
@@ -22,24 +21,32 @@
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULAppEnvironmentUtil.h"
@interface GULAppEnvironmentUtil (Tests)
+ (void)setProcessInfoForTest:(NSProcessInfo *)processInfo;
@end
@interface GULFakeNSProcessInfo : NSProcessInfo
@property(nonatomic) NSOperatingSystemVersion fakeOperatingSystemVersion;
@end
@implementation GULFakeNSProcessInfo
- (NSOperatingSystemVersion)operatingSystemVersion {
return self.fakeOperatingSystemVersion;
}
@end
@interface GULAppEnvironmentUtilTest : XCTestCase
@property(nonatomic) id processInfoMock;
@end
@implementation GULAppEnvironmentUtilTest
- (void)setUp {
[super setUp];
_processInfoMock = OCMPartialMock([NSProcessInfo processInfo]);
}
- (void)tearDown {
[super tearDown];
[_processInfoMock stopMocking];
[GULAppEnvironmentUtil setProcessInfoForTest:nil];
}
- (void)testSystemVersionInfoMajorOnly {
@@ -47,8 +54,10 @@
XCTAssertEqualObjects([GULAppEnvironmentUtil systemVersion],
[UIDevice currentDevice].systemVersion);
#else
GULFakeNSProcessInfo *fakeProcessInfo = [[GULFakeNSProcessInfo alloc] init];
NSOperatingSystemVersion osTen = {.majorVersion = 10, .minorVersion = 0, .patchVersion = 0};
OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTen);
fakeProcessInfo.fakeOperatingSystemVersion = osTen;
[GULAppEnvironmentUtil setProcessInfoForTest:fakeProcessInfo];
XCTAssertEqualObjects([GULAppEnvironmentUtil systemVersion], @"10.0");
#endif
}
@@ -58,8 +67,10 @@
XCTAssertEqualObjects([GULAppEnvironmentUtil systemVersion],
[UIDevice currentDevice].systemVersion);
#else
GULFakeNSProcessInfo *fakeProcessInfo = [[GULFakeNSProcessInfo alloc] init];
NSOperatingSystemVersion osTenTwo = {.majorVersion = 10, .minorVersion = 2, .patchVersion = 0};
OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTenTwo);
fakeProcessInfo.fakeOperatingSystemVersion = osTenTwo;
[GULAppEnvironmentUtil setProcessInfoForTest:fakeProcessInfo];
XCTAssertEqualObjects([GULAppEnvironmentUtil systemVersion], @"10.2");
#endif
}
@@ -69,8 +80,10 @@
XCTAssertEqualObjects([GULAppEnvironmentUtil systemVersion],
[UIDevice currentDevice].systemVersion);
#else
GULFakeNSProcessInfo *fakeProcessInfo = [[GULFakeNSProcessInfo alloc] init];
NSOperatingSystemVersion osTenTwoOne = {.majorVersion = 10, .minorVersion = 2, .patchVersion = 1};
OCMStub([self.processInfoMock operatingSystemVersion]).andReturn(osTenTwoOne);
fakeProcessInfo.fakeOperatingSystemVersion = osTenTwoOne;
[GULAppEnvironmentUtil setProcessInfoForTest:fakeProcessInfo];
XCTAssertEqualObjects([GULAppEnvironmentUtil systemVersion], @"10.2.1");
#endif
}
@@ -28,7 +28,6 @@
#import <XCTest/XCTest.h>
#import <OCMock/OCMock.h>
#import "GoogleUtilities/Tests/Unit/Utils/GULTestKeychain.h"
#import "GoogleUtilities/Environment/Public/GoogleUtilities/GULKeychainStorage.h"
@@ -38,10 +37,115 @@
- (void)resetInMemoryCache;
@end
@interface GULFakeNSCache : NSCache
@property(nonatomic, assign) BOOL forceCacheMiss;
@property(nonatomic, assign) NSInteger objectForKeyCallCount;
@property(nonatomic, strong) id lastObjectForKey;
@property(nonatomic, assign) NSInteger setObjectCallCount;
@property(nonatomic, strong) id lastSetObjectKey;
@property(nonatomic, strong) id lastSetObject;
@property(nonatomic, assign) NSInteger removeObjectCallCount;
@property(nonatomic, strong) id lastRemoveObjectKey;
- (void)resetTrackers;
- (NSInteger)syncObjectForKeyCallCount;
- (id)syncLastObjectForKey;
- (NSInteger)syncSetObjectCallCount;
- (id)syncLastSetObjectKey;
- (id)syncLastSetObject;
- (NSInteger)syncRemoveObjectCallCount;
- (id)syncLastRemoveObjectKey;
@end
@implementation GULFakeNSCache
- (id)objectForKey:(id)key {
@synchronized(self) {
_objectForKeyCallCount++;
_lastObjectForKey = key;
if (_forceCacheMiss) {
return nil;
}
}
return [super objectForKey:key];
}
- (void)setObject:(id)obj forKey:(id)key {
@synchronized(self) {
_setObjectCallCount++;
_lastSetObjectKey = key;
_lastSetObject = obj;
}
[super setObject:obj forKey:key];
}
- (void)removeObjectForKey:(id)key {
@synchronized(self) {
_removeObjectCallCount++;
_lastRemoveObjectKey = key;
}
[super removeObjectForKey:key];
}
- (void)resetTrackers {
@synchronized(self) {
_objectForKeyCallCount = 0;
_lastObjectForKey = nil;
_setObjectCallCount = 0;
_lastSetObjectKey = nil;
_lastSetObject = nil;
_removeObjectCallCount = 0;
_lastRemoveObjectKey = nil;
}
}
- (NSInteger)syncObjectForKeyCallCount {
@synchronized(self) {
return _objectForKeyCallCount;
}
}
- (id)syncLastObjectForKey {
@synchronized(self) {
return _lastObjectForKey;
}
}
- (NSInteger)syncSetObjectCallCount {
@synchronized(self) {
return _setObjectCallCount;
}
}
- (id)syncLastSetObjectKey {
@synchronized(self) {
return _lastSetObjectKey;
}
}
- (id)syncLastSetObject {
@synchronized(self) {
return _lastSetObject;
}
}
- (NSInteger)syncRemoveObjectCallCount {
@synchronized(self) {
return _removeObjectCallCount;
}
}
- (id)syncLastRemoveObjectKey {
@synchronized(self) {
return _lastRemoveObjectKey;
}
}
@end
@interface GULKeychainStorageTests : XCTestCase
@property(nonatomic, strong) GULKeychainStorage *storage;
@property(nonatomic, strong) NSCache *cache;
@property(nonatomic, strong) id mockCache;
@property(nonatomic, strong) GULFakeNSCache *fakeCache;
#if TARGET_OS_OSX
@property(nonatomic) GULTestKeychain *privateKeychain;
@@ -52,10 +156,9 @@
@implementation GULKeychainStorageTests
- (void)setUp {
self.cache = [[NSCache alloc] init];
self.mockCache = OCMPartialMock(self.cache);
self.fakeCache = [[GULFakeNSCache alloc] init];
self.storage = [[GULKeychainStorage alloc] initWithService:@"com.tests.GULKeychainStorageTests"
cache:self.mockCache];
cache:self.fakeCache];
#if TARGET_OS_OSX
self.privateKeychain = [[GULTestKeychain alloc] init];
@@ -65,8 +168,7 @@
- (void)tearDown {
self.storage = nil;
self.mockCache = nil;
self.cache = nil;
self.fakeCache = nil;
#if TARGET_OS_OSX
self.privateKeychain = nil;
@@ -89,7 +191,7 @@
existsInCache:YES];
// 3. Read existing object which is not present in in-memory cache.
[self.cache removeAllObjects];
[self.fakeCache removeAllObjects];
// TODO: Evaluate if GULKeychainStorage needs an API that takes set of classes. (#42)
// The following method causes an NSKeyedUnarchiver-related runtime warning log.
[self assertSuccessReadObject:@{@"key" : @"value"}
@@ -117,7 +219,8 @@
// Read.
// Skip in-memory cache because the error is relevant only for Keychain.
OCMExpect([self.mockCache objectForKey:key]).andReturn(nil);
self.fakeCache.forceCacheMiss = YES;
[self.fakeCache resetTrackers];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
[self.storage getObjectForKey:key
@@ -130,7 +233,7 @@
XCTAssertEqual(error.domain, NSCocoaErrorDomain);
XCTAssertEqual(error.code, 4864);
OCMVerifyAll(self.mockCache);
XCTAssertEqual([self.fakeCache syncObjectForKeyCallCount], 1);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5.0];
@@ -157,7 +260,7 @@
#pragma mark - Common
- (void)assertSuccessWriteObject:(id<NSSecureCoding>)object forKey:(NSString *)key {
OCMExpect([self.mockCache setObject:object forKey:key]).andForwardToRealObject();
[self.fakeCache resetTrackers];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
__weak __auto_type weakSelf = self;
@@ -169,47 +272,46 @@
return;
}
XCTAssertNil(error, @"%@", weakSelf.name);
// Check in-memory cache.
XCTAssertEqualObjects([weakSelf.cache objectForKey:key], object);
XCTAssertEqual([weakSelf.fakeCache syncSetObjectCallCount], 1);
XCTAssertEqualObjects([weakSelf.fakeCache syncLastSetObjectKey], key);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5.0];
OCMVerifyAll(self.mockCache);
}
- (void)assertSuccessReadObject:(id<NSSecureCoding>)object
forKey:(NSString *)key
class:(Class)class
existsInCache:(BOOL)existisInCache {
OCMExpect([self.mockCache objectForKey:key]).andForwardToRealObject();
if (!existisInCache) {
OCMExpect([self.mockCache setObject:object forKey:key]).andForwardToRealObject();
}
[self.fakeCache resetTrackers];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
__weak __auto_type weakSelf = self;
[self.storage
getObjectForKey:key
objectClass:class
accessGroup:nil
completionHandler:^(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error) {
if (!weakSelf) {
return;
}
XCTAssertEqualObjects(obj, object, @"%@", weakSelf.name);
XCTAssertNil(error, @"%@", weakSelf.name);
// Check in-memory cache.
XCTAssertEqualObjects([weakSelf.cache objectForKey:key], object, @"%@", weakSelf.name);
[expectation fulfill];
}];
[self.storage getObjectForKey:key
objectClass:class
accessGroup:nil
completionHandler:^(id<NSSecureCoding> _Nullable obj, NSError *_Nullable error) {
if (!weakSelf) {
return;
}
XCTAssertEqualObjects(obj, object, @"%@", weakSelf.name);
XCTAssertNil(error, @"%@", weakSelf.name);
XCTAssertEqual([weakSelf.fakeCache syncObjectForKeyCallCount], 1);
XCTAssertEqualObjects([weakSelf.fakeCache syncLastObjectForKey], key);
if (!existisInCache) {
XCTAssertEqual([weakSelf.fakeCache syncSetObjectCallCount], 1);
XCTAssertEqualObjects([weakSelf.fakeCache syncLastSetObjectKey], key);
}
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5.0];
OCMVerifyAll(self.mockCache);
}
- (void)assertNonExistingObjectForKey:(NSString *)key class:(Class)class {
OCMExpect([self.mockCache objectForKey:key]).andForwardToRealObject();
[self.fakeCache resetTrackers];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
__weak __auto_type weakSelf = self;
@@ -222,24 +324,27 @@
}
XCTAssertNil(error, @"%@", weakSelf.name);
XCTAssertNil(obj, @"%@", weakSelf.name);
XCTAssertEqual([weakSelf.fakeCache syncObjectForKeyCallCount], 1);
XCTAssertEqualObjects([weakSelf.fakeCache syncLastObjectForKey], key);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5.0];
OCMVerifyAll(self.mockCache);
}
- (void)assertRemoveObjectForKey:(NSString *)key {
OCMExpect([self.mockCache removeObjectForKey:key]).andForwardToRealObject();
[self.fakeCache resetTrackers];
XCTestExpectation *expectation = [self expectationWithDescription:NSStringFromSelector(_cmd)];
__weak __auto_type weakSelf = self;
[self.storage removeObjectForKey:key
accessGroup:nil
completionHandler:^(NSError *_Nullable error) {
XCTAssertNil(error);
XCTAssertEqual([weakSelf.fakeCache syncRemoveObjectCallCount], 1);
XCTAssertEqualObjects([weakSelf.fakeCache syncLastRemoveObjectKey], key);
[expectation fulfill];
}];
[self waitForExpectations:@[ expectation ] timeout:5.0];
OCMVerifyAll(self.mockCache);
}
@end
@@ -15,7 +15,6 @@
#ifdef DEBUG
// The tests depend upon library methods only built with #ifdef DEBUG
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h"
@@ -14,7 +14,6 @@
#import "GoogleUtilities/Tests/Unit/Network/third_party/GTMHTTPServer.h"
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#if !TARGET_OS_MACCATALYST
@@ -100,26 +99,19 @@
id reachability = [_network valueForKey:@"_reachability"];
XCTAssertNotNil(reachability);
id reachabilityMock = OCMPartialMock(reachability);
[[[reachabilityMock stub] andCall:@selector(reachabilityStatus)
onObject:self] reachabilityStatus];
// Fake scenario with connectivity.
_fakeNetworkIsReachable = YES;
_fakeReachabilityStatus = kGULReachabilityViaWifi;
[_network reachability:reachabilityMock statusChanged:[reachabilityMock reachabilityStatus]];
[_network reachability:reachability statusChanged:kGULReachabilityViaWifi];
XCTAssertTrue([_network isNetworkConnected]);
XCTAssertEqual(_currentNetworkStatus, _fakeNetworkIsReachable);
// Fake scenario without connectivity.
_fakeNetworkIsReachable = NO;
_fakeReachabilityStatus = kGULReachabilityNotReachable;
[_network reachability:reachabilityMock statusChanged:[reachabilityMock reachabilityStatus]];
[_network reachability:reachability statusChanged:kGULReachabilityNotReachable];
XCTAssertFalse([_network isNetworkConnected]);
XCTAssertEqual(_currentNetworkStatus, _fakeNetworkIsReachable);
[reachabilityMock stopMocking];
reachabilityMock = nil;
}
#pragma mark - Test Passive Deallocation
@@ -1041,28 +1033,23 @@
XCTAssertTrue([fileManager fileExistsAtPath:tempFile1.path]);
XCTAssertTrue([fileManager fileExistsAtPath:tempFile2.path]);
NSDate *now =
[[NSDate date] dateByAddingTimeInterval:1]; // Start mocking the clock to avoid flakiness.
id mockDate = OCMStrictClassMock([NSDate class]);
[[[mockDate stub] andReturn:now] date];
// The file should not be removed since it is not expired yet.
[session maybeRemoveTempFilesAtURL:folderURL expiringTime:20];
XCTAssertTrue([fileManager fileExistsAtPath:tempFile1.path]);
XCTAssertTrue([fileManager fileExistsAtPath:tempFile2.path]);
[mockDate stopMocking];
mockDate = nil;
now = [[NSDate date] dateByAddingTimeInterval:100]; // Move forward in time 100s.
mockDate = OCMStrictClassMock([NSDate class]);
[[[mockDate stub] andReturn:now] date];
// Backdate the files to 100 seconds in the past
NSDate *pastDate = [[NSDate date] dateByAddingTimeInterval:-100];
[fileManager setAttributes:@{NSFileCreationDate : pastDate}
ofItemAtPath:tempFile1.path
error:nil];
[fileManager setAttributes:@{NSFileCreationDate : pastDate}
ofItemAtPath:tempFile2.path
error:nil];
[session maybeRemoveTempFilesAtURL:folderURL expiringTime:20];
XCTAssertFalse([fileManager fileExistsAtPath:tempFile1.path]);
XCTAssertFalse([fileManager fileExistsAtPath:tempFile2.path]);
[mockDate stopMocking];
mockDate = nil;
}
#pragma mark - Internal Methods
@@ -1112,14 +1099,6 @@
statusCode:_statusCode];
}
- (BOOL)isReachable {
return _fakeNetworkIsReachable;
}
- (GULReachabilityStatus)reachabilityStatus {
return _fakeReachabilityStatus;
}
#pragma mark - FIRReachabilityDelegate
- (void)reachabilityDidChange {
@@ -1,35 +0,0 @@
/*
* Copyright 2020 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef BOOL (^FIRRequestValidationBlock)(NSURLRequest *request);
@interface FIRURLSessionOCMockStub : NSObject
+ (id)stubURLSessionDataTaskWithResponse:(nullable NSHTTPURLResponse *)response
body:(nullable NSData *)body
error:(nullable NSError *)error
URLSessionMock:(id)URLSessionMock
requestValidationBlock:(nullable FIRRequestValidationBlock)requestValidationBlock;
+ (NSHTTPURLResponse *)HTTPResponseWithCode:(NSInteger)statusCode;
@end
NS_ASSUME_NONNULL_END
@@ -1,65 +0,0 @@
/*
* Copyright 2020 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 "GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.h"
#import <OCMock/OCMock.h>
@implementation FIRURLSessionOCMockStub
+ (id)stubURLSessionDataTaskWithResponse:(NSHTTPURLResponse *)response
body:(NSData *)body
error:(NSError *)error
URLSessionMock:(id)URLSessionMock
requestValidationBlock:(FIRRequestValidationBlock)requestValidationBlock {
id mockDataTask = OCMStrictClassMock([NSURLSessionDataTask class]);
// Validate request content.
FIRRequestValidationBlock nonOptionalRequestValidationBlock =
requestValidationBlock ?: ^BOOL(id request) {
return YES;
};
id URLRequestValidationArg = [OCMArg checkWithBlock:nonOptionalRequestValidationBlock];
// Save task completion to be called on the `[NSURLSessionDataTask resume]`
__block void (^taskCompletion)(NSData *, NSURLResponse *, NSError *);
id completionArg = [OCMArg checkWithBlock:^BOOL(id obj) {
taskCompletion = obj;
return YES;
}];
// Expect `dataTaskWithRequest` to be called.
OCMExpect([URLSessionMock dataTaskWithRequest:URLRequestValidationArg
completionHandler:completionArg])
.andReturn(mockDataTask);
// Expect the task to be resumed and call the task completion.
OCMExpect([(NSURLSessionDataTask *)mockDataTask resume]).andDo(^(NSInvocation *invocation) {
taskCompletion(body, response, error);
});
return mockDataTask;
}
+ (NSHTTPURLResponse *)HTTPResponseWithCode:(NSInteger)statusCode {
return [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"http://localhost"]
statusCode:statusCode
HTTPVersion:@"HTTP/1.1"
headerFields:nil];
}
@end
@@ -15,7 +15,6 @@
#import "GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h"
#import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h"
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
@@ -240,28 +239,166 @@ static BOOL gRespondsToHandleBackgroundSession;
@end
@interface GULFakeApplication : NSObject
@property(nonatomic, strong) id<GULApplicationDelegate> delegate;
@end
@implementation GULFakeApplication
@end
static GULFakeApplication *gFakeApplication;
@interface GULApplication (FakeShared)
+ (GULApplication *)gul_fakeSharedApplication;
@end
@implementation GULApplication (FakeShared)
+ (GULApplication *)gul_fakeSharedApplication {
return (GULApplication *)gFakeApplication;
}
@end
static NSDictionary *gAppFakeInfoDictionary;
@interface NSBundle (AppFakeInfoDictionary)
- (NSDictionary *)gul_app_fakeInfoDictionary;
@end
@implementation NSBundle (AppFakeInfoDictionary)
- (NSDictionary *)gul_app_fakeInfoDictionary {
if (gAppFakeInfoDictionary) return gAppFakeInfoDictionary;
return [self gul_app_fakeInfoDictionary];
}
@end
@interface GULFakeAppDelegateInterceptor : NSObject <GULApplicationDelegate>
@property(nonatomic) BOOL shouldReturnYES;
@property(nonatomic) BOOL isApplicationOpenURLOptionsCalled;
@property(nonatomic) BOOL isApplicationHandleEventsForBackgroundURLSessionCalled;
@property(nonatomic) BOOL isApplicationContinueUserActivityCalled;
@property(nonatomic) BOOL isApplicationDidRegisterForRemoteNotificationsCalled;
@property(nonatomic) BOOL isApplicationDidFailToRegisterForRemoteNotificationsCalled;
@property(nonatomic) BOOL isApplicationDidReceiveRemoteNotificationWithCompletionCalled;
@property(nonatomic, strong) dispatch_queue_t syncQueue;
#if (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST
@property(nonatomic, copy) void (^onDidReceiveRemoteNotificationWithCompletion)
(NSDictionary *userInfo, void (^completionHandler)(UIBackgroundFetchResult));
#endif
@end
@implementation GULFakeAppDelegateInterceptor
- (instancetype)init {
self = [super init];
if (self) {
_syncQueue = dispatch_queue_create("GULFakeAppDelegateInterceptor", DISPATCH_QUEUE_SERIAL);
}
return self;
}
#if TARGET_OS_IOS || TARGET_OS_TV
- (BOOL)application:(GULApplication *)app
openURL:(NSURL *)url
options:(NSDictionary<NSString *, id> *)options {
dispatch_sync(_syncQueue, ^{
self->_isApplicationOpenURLOptionsCalled = YES;
});
return self.shouldReturnYES;
}
- (void)application:(GULApplication *)application
handleEventsForBackgroundURLSession:(nonnull NSString *)identifier
completionHandler:(nonnull void (^)(void))completionHandler {
dispatch_sync(_syncQueue, ^{
self->_isApplicationHandleEventsForBackgroundURLSessionCalled = YES;
});
}
#endif
#if SDK_HAS_USERACTIVITY
- (BOOL)application:(GULApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray<id<GULUserActivityRestoring>> *__nullable
restorableObjects))restorationHandler {
dispatch_sync(_syncQueue, ^{
self->_isApplicationContinueUserActivityCalled = YES;
});
return self.shouldReturnYES;
}
#endif
- (void)application:(GULApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
dispatch_sync(_syncQueue, ^{
self->_isApplicationDidRegisterForRemoteNotificationsCalled = YES;
});
}
- (void)application:(GULApplication *)application
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
dispatch_sync(_syncQueue, ^{
self->_isApplicationDidFailToRegisterForRemoteNotificationsCalled = YES;
});
}
#if (TARGET_OS_IOS || TARGET_OS_TV) && !TARGET_OS_MACCATALYST
- (void)application:(GULApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
dispatch_sync(_syncQueue, ^{
self->_isApplicationDidReceiveRemoteNotificationWithCompletionCalled = YES;
});
if (self.onDidReceiveRemoteNotificationWithCompletion) {
self.onDidReceiveRemoteNotificationWithCompletion(userInfo, completionHandler);
}
}
#endif
@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);
gFakeApplication = [[GULFakeApplication alloc] init];
Method originalMethod =
class_getClassMethod([GULApplication class], @selector(sharedApplication));
Method swizzledMethod =
class_getClassMethod([GULApplication class], @selector(gul_fakeSharedApplication));
method_exchangeImplementations(originalMethod, swizzledMethod);
Method originalBundleMethod =
class_getInstanceMethod([NSBundle class], @selector(infoDictionary));
Method swizzledBundleMethod =
class_getInstanceMethod([NSBundle class], @selector(gul_app_fakeInfoDictionary));
method_exchangeImplementations(originalBundleMethod, swizzledBundleMethod);
}
- (void)tearDown {
[GULAppDelegateSwizzler clearInterceptors];
[GULAppDelegateSwizzler resetProxyOriginalDelegateOnceToken];
self.mockSharedApplication = nil;
Method originalMethod =
class_getClassMethod([GULApplication class], @selector(sharedApplication));
Method swizzledMethod =
class_getClassMethod([GULApplication class], @selector(gul_fakeSharedApplication));
method_exchangeImplementations(originalMethod, swizzledMethod);
gFakeApplication = nil;
Method originalBundleMethod =
class_getInstanceMethod([NSBundle class], @selector(infoDictionary));
Method swizzledBundleMethod =
class_getInstanceMethod([NSBundle class], @selector(gul_app_fakeInfoDictionary));
method_exchangeImplementations(originalBundleMethod, swizzledBundleMethod);
gAppFakeInfoDictionary = nil;
[super tearDown];
}
- (void)testNotAppDelegateIsNotSwizzled {
NSObject *notAppDelegate = [[NSObject alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(notAppDelegate);
[GULApplication sharedApplication].delegate = (id<GULApplicationDelegate>)notAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegate];
XCTAssertEqualObjects(NSStringFromClass([notAppDelegate class]), @"NSObject");
}
@@ -272,7 +409,7 @@ static BOOL gRespondsToHandleBackgroundSession;
*/
- (void)testProxyAppDelegate {
GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
[GULApplication sharedApplication].delegate = realAppDelegate;
size_t sizeBefore = class_getInstanceSize([GULTestAppDelegate class]);
Class realAppDelegateClassBefore = [realAppDelegate class];
@@ -323,7 +460,7 @@ static BOOL gRespondsToHandleBackgroundSession;
- (void)testProxyEmptyAppDelegate {
GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
[GULApplication sharedApplication].delegate = realAppDelegate;
size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
Class realAppDelegateClassBefore = [realAppDelegate class];
@@ -370,7 +507,7 @@ static BOOL gRespondsToHandleBackgroundSession;
- (void)testProxyRemoteNotificationsMethodsEmptyAppDelegate {
GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
[GULApplication sharedApplication].delegate = realAppDelegate;
size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
Class realAppDelegateClassBefore = [realAppDelegate class];
@@ -420,7 +557,7 @@ static BOOL gRespondsToHandleBackgroundSession;
- (void)testProxyRemoteNotificationsMethodsEmptyAppDelegateAfterInitialProxy {
GULEmptyTestAppDelegate *realAppDelegate = [[GULEmptyTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
[GULApplication sharedApplication].delegate = realAppDelegate;
size_t sizeBefore = class_getInstanceSize([GULEmptyTestAppDelegate class]);
Class realAppDelegateClassBefore = [realAppDelegate class];
@@ -469,7 +606,7 @@ static BOOL gRespondsToHandleBackgroundSession;
#if SDK_HAS_USERACTIVITY
- (void)testHandleBackgroundSessionMethod {
GULTestAppDelegate *realAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(realAppDelegate);
[GULApplication sharedApplication].delegate = realAppDelegate;
// Create the proxy.
[GULAppDelegateSwizzler proxyOriginalDelegate];
@@ -539,7 +676,7 @@ static BOOL gRespondsToHandleBackgroundSession;
/** 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);
[GULApplication sharedApplication].delegate = realAppDelegate;
Class classBefore = [realAppDelegate class];
NSString *descriptionBefore = [realAppDelegate description];
@@ -560,7 +697,7 @@ static BOOL gRespondsToHandleBackgroundSession;
/** 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);
[GULApplication sharedApplication].delegate = realAppDelegate;
// Create the proxy.
[GULAppDelegateSwizzler proxyOriginalDelegate];
@@ -586,8 +723,6 @@ static BOOL gRespondsToHandleBackgroundSession;
// 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];
@@ -615,19 +750,14 @@ static BOOL gRespondsToHandleBackgroundSession;
#if TARGET_OS_IOS || TARGET_OS_TV
/** Tests that application:openURL:options: is invoked on the interceptor if it exists. */
- (void)testApplicationOpenURLOptionsIsInvokedOnInterceptors {
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);
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
NSURL *testURL = [[NSURL alloc] initWithString:@"https://www.google.com"];
NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"};
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegate];
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
@@ -636,8 +766,18 @@ static BOOL gRespondsToHandleBackgroundSession;
[testAppDelegate application:[GULApplication sharedApplication]
openURL:testURL
options:testOpenURLOptions];
OCMVerifyAll(interceptor);
OCMVerifyAll(interceptor2);
__block BOOL isCalled1 = NO;
__block BOOL isCalled2 = NO;
dispatch_sync(interceptor.syncQueue, ^{
isCalled1 = interceptor.isApplicationOpenURLOptionsCalled;
});
dispatch_sync(interceptor2.syncQueue, ^{
isCalled2 = interceptor2.isApplicationOpenURLOptionsCalled;
});
XCTAssertTrue(isCalled1);
XCTAssertTrue(isCalled2);
// Check that original implementation was called with proper parameters
XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
@@ -650,7 +790,7 @@ static BOOL gRespondsToHandleBackgroundSession;
NSDictionary *testOpenURLOptions = @{UIApplicationOpenURLOptionUniversalLinksOnly : @"test"};
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegate];
BOOL shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
@@ -659,9 +799,8 @@ static BOOL gRespondsToHandleBackgroundSession;
// 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);
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
interceptor.shouldReturnYES = NO;
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
shouldOpen = [testAppDelegate application:[GULApplication sharedApplication]
openURL:testURL
@@ -669,13 +808,10 @@ static BOOL gRespondsToHandleBackgroundSession;
// 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);
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
interceptor2.shouldReturnYES = 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];
@@ -689,18 +825,11 @@ static BOOL gRespondsToHandleBackgroundSession;
* 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]);
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegate];
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
@@ -712,8 +841,17 @@ static BOOL gRespondsToHandleBackgroundSession;
completionHandler:^{
}];
OCMVerifyAll(interceptor);
OCMVerifyAll(interceptor2);
__block BOOL isCalled1 = NO;
__block BOOL isCalled2 = NO;
dispatch_sync(interceptor.syncQueue, ^{
isCalled1 = interceptor.isApplicationHandleEventsForBackgroundURLSessionCalled;
});
dispatch_sync(interceptor2.syncQueue, ^{
isCalled2 = interceptor2.isApplicationHandleEventsForBackgroundURLSessionCalled;
});
XCTAssertTrue(isCalled1);
XCTAssertTrue(isCalled2);
// Check that original implementation was called with proper parameters
XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
@@ -721,26 +859,18 @@ static BOOL gRespondsToHandleBackgroundSession;
}
#endif // TARGET_OS_IOS || TARGET_OS_TV
#if SDK_HAS_USERACTIVITY
/** 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);
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"];
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegate];
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
@@ -750,8 +880,18 @@ static BOOL gRespondsToHandleBackgroundSession;
continueUserActivity:testUserActivity
restorationHandler:^(NSArray *restorableObjects){
}];
OCMVerifyAll(interceptor);
OCMVerifyAll(interceptor2);
__block BOOL isCalled1 = NO;
__block BOOL isCalled2 = NO;
dispatch_sync(interceptor.syncQueue, ^{
isCalled1 = interceptor.isApplicationContinueUserActivityCalled;
});
dispatch_sync(interceptor2.syncQueue, ^{
isCalled2 = interceptor2.isApplicationContinueUserActivityCalled;
});
XCTAssertTrue(isCalled1);
XCTAssertTrue(isCalled2);
// Check that original implementation was called with proper parameters
XCTAssertEqual(testAppDelegate.application, [GULApplication sharedApplication]);
@@ -763,7 +903,7 @@ static BOOL gRespondsToHandleBackgroundSession;
*/
- (void)testApplicationContinueUserActivityRestorationHandlerResultsAreORed {
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegate];
NSUserActivity *testUserActivity = [[NSUserActivity alloc] initWithActivityType:@"test"];
@@ -774,11 +914,8 @@ static BOOL gRespondsToHandleBackgroundSession;
// 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);
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
interceptor.shouldReturnYES = NO;
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
shouldContinueUserActivity = [testAppDelegate application:[GULApplication sharedApplication]
continueUserActivity:testUserActivity
@@ -787,40 +924,28 @@ static BOOL gRespondsToHandleBackgroundSession;
// 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);
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
interceptor2.shouldReturnYES = 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.
// Verify that if one of the two interceptors returns YES, the value is YES.
XCTAssertTrue(shouldContinueUserActivity);
}
#endif // SDK_HAS_USERACTIVITY
- (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]);
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
@@ -828,8 +953,18 @@ static BOOL gRespondsToHandleBackgroundSession;
[testAppDelegate application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
OCMVerifyAll(interceptor);
OCMVerifyAll(interceptor2);
__block BOOL isCalled1 = NO;
__block BOOL isCalled2 = NO;
dispatch_sync(interceptor.syncQueue, ^{
isCalled1 = interceptor.isApplicationDidRegisterForRemoteNotificationsCalled;
});
dispatch_sync(interceptor2.syncQueue, ^{
isCalled2 = interceptor2.isApplicationDidRegisterForRemoteNotificationsCalled;
});
XCTAssertTrue(isCalled1);
XCTAssertTrue(isCalled2);
XCTAssertEqual(testAppDelegate.application, application);
XCTAssertEqual(testAppDelegate.remoteNotificationsDeviceToken, deviceToken);
@@ -839,24 +974,29 @@ static BOOL gRespondsToHandleBackgroundSession;
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]);
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor2];
[testAppDelegate application:application didFailToRegisterForRemoteNotificationsWithError:error];
OCMVerifyAll(interceptor);
OCMVerifyAll(interceptor2);
__block BOOL isCalled1 = NO;
__block BOOL isCalled2 = NO;
dispatch_sync(interceptor.syncQueue, ^{
isCalled1 = interceptor.isApplicationDidFailToRegisterForRemoteNotificationsCalled;
});
dispatch_sync(interceptor2.syncQueue, ^{
isCalled2 = interceptor2.isApplicationDidFailToRegisterForRemoteNotificationsCalled;
});
XCTAssertTrue(isCalled1);
XCTAssertTrue(isCalled2);
XCTAssertEqual(testAppDelegate.application, application);
XCTAssertEqual(testAppDelegate.failToRegisterForRemoteNotificationsError, error);
@@ -869,18 +1009,11 @@ static BOOL gRespondsToHandleBackgroundSession;
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]]);
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
@@ -889,8 +1022,18 @@ static BOOL gRespondsToHandleBackgroundSession;
[testAppDelegate application:application
didReceiveRemoteNotification:notification
fetchCompletionHandler:completion];
OCMVerifyAll(interceptor);
OCMVerifyAll(interceptor2);
__block BOOL isCalled1 = NO;
__block BOOL isCalled2 = NO;
dispatch_sync(interceptor.syncQueue, ^{
isCalled1 = interceptor.isApplicationDidReceiveRemoteNotificationWithCompletionCalled;
});
dispatch_sync(interceptor2.syncQueue, ^{
isCalled2 = interceptor2.isApplicationDidReceiveRemoteNotificationWithCompletionCalled;
});
XCTAssertTrue(isCalled1);
XCTAssertTrue(isCalled2);
XCTAssertEqual(testAppDelegate.application, application);
XCTAssertEqual(testAppDelegate.remoteNotification, notification);
@@ -911,34 +1054,20 @@ static BOOL gRespondsToHandleBackgroundSession;
[completionExpectation fulfill];
};
void (^onDidReceiveRemoteNotification1)(NSInvocation *invocation) = ^(NSInvocation *invocation) {
void __unsafe_unretained (^localCompletionHandler)(UIBackgroundFetchResult) = nil;
[invocation getArgument:(void *)(&localCompletionHandler) atIndex:4];
XCTAssertNotNil(localCompletionHandler);
localCompletionHandler(observerResult1);
};
GULFakeAppDelegateInterceptor *interceptor = [[GULFakeAppDelegateInterceptor alloc] init];
interceptor.onDidReceiveRemoteNotificationWithCompletion =
^(NSDictionary *userInfo, void (^completionHandler)(UIBackgroundFetchResult)) {
completionHandler(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);
GULFakeAppDelegateInterceptor *interceptor2 = [[GULFakeAppDelegateInterceptor alloc] init];
interceptor2.onDidReceiveRemoteNotificationWithCompletion =
^(NSDictionary *userInfo, void (^completionHandler)(UIBackgroundFetchResult)) {
completionHandler(observerResult2);
};
GULTestAppDelegate *testAppDelegate = [[GULTestAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(testAppDelegate);
[GULApplication sharedApplication].delegate = testAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
[GULAppDelegateSwizzler registerAppDelegateInterceptor:interceptor];
@@ -948,8 +1077,6 @@ static BOOL gRespondsToHandleBackgroundSession;
didReceiveRemoteNotification:notification
fetchCompletionHandler:completion];
testAppDelegate.remoteNotificationCompletionHandler(swizzledResult);
OCMVerifyAll(interceptor);
OCMVerifyAll(interceptor2);
[self waitForExpectations:@[ completionExpectation ] timeout:0.1];
}
@@ -1002,7 +1129,7 @@ static BOOL gRespondsToHandleBackgroundSession;
// The delegate must have an application:didReceiveRemoteNotification:fetchCompletionHandler:
// implementation
GULTestInterceptorAppDelegate *delegate = [[GULTestInterceptorAppDelegate alloc] init];
OCMStub([self.mockSharedApplication delegate]).andReturn(delegate);
[GULApplication sharedApplication].delegate = delegate;
XCTAssertFalse([delegate
respondsToSelector:@selector(
@@ -1020,191 +1147,105 @@ static BOOL gRespondsToHandleBackgroundSession;
/** 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];
gAppFakeInfoDictionary = nil;
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];
gAppFakeInfoDictionary = @{@"randomKey" : @"randomValue"};
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];
gAppFakeInfoDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(YES)};
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];
gAppFakeInfoDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @(YES)};
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];
gAppFakeInfoDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @"NO"};
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];
gAppFakeInfoDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @"NO"};
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];
gAppFakeInfoDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)};
NSLog(@"gAppFakeInfoDictionary: %@", gAppFakeInfoDictionary);
NSLog(@"mainBundle: %@", [NSBundle mainBundle]);
NSLog(@"infoDictionary: %@", [NSBundle mainBundle].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];
gAppFakeInfoDictionary = @{kGULGoogleAppDelegateProxyEnabledPlistKey : @(NO)};
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 = @{
gAppFakeInfoDictionary = @{
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 = @{
gAppFakeInfoDictionary = @{
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 = @{
gAppFakeInfoDictionary = @{
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];
gAppFakeInfoDictionary = @{kGULFirebaseAppDelegateProxyEnabledPlistKey : @(NO)};
XCTAssertFalse([GULAppDelegateSwizzler isAppDelegateProxyEnabled]);
id originalAppDelegate = OCMProtocolMock(@protocol(GULApplicationDelegate));
id originalAppDelegate = [[GULTestAppDelegate alloc] init];
Class originalAppDelegateClass = [originalAppDelegate class];
XCTAssertNotNil(originalAppDelegate);
OCMStub([self.mockSharedApplication delegate]).andReturn(originalAppDelegate);
[GULApplication sharedApplication].delegate = 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);
[GULApplication sharedApplication].delegate = 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);
[GULApplication sharedApplication].delegate = originalAppDelegate;
[GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods];
XCTAssertNotEqualObjects([originalAppDelegate class], originalAppDelegateClass);
@@ -15,7 +15,6 @@
#import "GoogleUtilities/AppDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h"
#import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULSceneDelegateSwizzler.h"
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
@@ -35,6 +34,8 @@ static NSString *const kGULGoogleSceneDelegateProxyEnabledPlistKey =
#if UISCENE_SUPPORTED
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
@protocol TestSceneProtocol <UISceneDelegate>
@end
@@ -45,31 +46,103 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
@implementation GULTestSceneDelegate
@end
API_AVAILABLE(ios(13.0), tvos(13.0))
@interface GULFakeSceneDelegateInterceptor : NSObject <UISceneDelegate>
@property(nonatomic) dispatch_queue_t syncQueue;
@property(nonatomic) BOOL isSceneWillConnectToSessionOptionsCalled;
@property(nonatomic) BOOL isSceneOpenURLContextsCalled;
@end
@implementation GULFakeSceneDelegateInterceptor
- (instancetype)init {
self = [super init];
if (self) {
_syncQueue = dispatch_queue_create("GULFakeSceneDelegateInterceptor", DISPATCH_QUEUE_SERIAL);
}
return self;
}
- (void)scene:(UIScene *)scene
willConnectToSession:(UISceneSession *)session
options:(UISceneConnectionOptions *)connectionOptions {
dispatch_sync(_syncQueue, ^{
self->_isSceneWillConnectToSessionOptionsCalled = YES;
});
}
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
dispatch_sync(_syncQueue, ^{
self->_isSceneOpenURLContextsCalled = YES;
});
}
@end
API_AVAILABLE(ios(13.0), tvos(13.0))
@interface GULFakeScene : NSObject
@property(nonatomic, weak) id<UISceneDelegate> delegate;
@end
@implementation GULFakeScene
@synthesize delegate = _delegate;
@end
static NSDictionary *gSceneFakeInfoDictionary = nil;
@interface NSBundle (SceneFakeInfoDictionary)
- (NSDictionary *)gul_scene_fakeInfoDictionary;
@end
@implementation NSBundle (SceneFakeInfoDictionary)
- (NSDictionary *)gul_scene_fakeInfoDictionary {
if (gSceneFakeInfoDictionary) return gSceneFakeInfoDictionary;
return [self gul_scene_fakeInfoDictionary];
}
@end
@interface GULSceneDelegateSwizzlerTest : XCTestCase
@end
@implementation GULSceneDelegateSwizzlerTest
- (void)setUp {
[super setUp];
Method originalBundleMethod =
class_getInstanceMethod([NSBundle class], @selector(infoDictionary));
Method swizzledBundleMethod =
class_getInstanceMethod([NSBundle class], @selector(gul_scene_fakeInfoDictionary));
method_exchangeImplementations(originalBundleMethod, swizzledBundleMethod);
}
- (void)tearDown {
[GULSceneDelegateSwizzler clearInterceptors];
Method originalBundleMethod =
class_getInstanceMethod([NSBundle class], @selector(infoDictionary));
Method swizzledBundleMethod =
class_getInstanceMethod([NSBundle class], @selector(gul_scene_fakeInfoDictionary));
method_exchangeImplementations(originalBundleMethod, swizzledBundleMethod);
gSceneFakeInfoDictionary = nil;
[super tearDown];
}
- (void)testProxySceneDelegateWithNoSceneDelegate {
if (@available(iOS 13, tvOS 13, *)) {
id mockSharedScene = OCMClassMock([UIScene class]);
OCMStub([mockSharedScene delegate]).andReturn(nil);
XCTAssertNoThrow([GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene]);
[mockSharedScene stopMocking];
mockSharedScene = nil;
GULFakeScene *mockSharedScene = [[GULFakeScene alloc] init];
mockSharedScene.delegate = nil;
XCTAssertNoThrow(
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:(UIScene *)mockSharedScene]);
}
}
- (void)testProxySceneDelegate {
if (@available(iOS 13, tvOS 13, *)) {
GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
id mockSharedScene = OCMClassMock([UIScene class]);
OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
GULFakeScene *mockSharedScene = [[GULFakeScene alloc] init];
mockSharedScene.delegate = realSceneDelegate;
size_t sizeBefore = class_getInstanceSize([GULTestSceneDelegate class]);
Class realSceneDelegateClassBefore = [realSceneDelegate class];
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:(UIScene *)mockSharedScene];
XCTAssertTrue([realSceneDelegate isKindOfClass:[GULTestSceneDelegate class]]);
@@ -88,31 +161,25 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
// Make sure that the class has changed.
XCTAssertNotEqualObjects([realSceneDelegate class], realSceneDelegateClassBefore);
[mockSharedScene stopMocking];
mockSharedScene = nil;
}
}
- (void)testProxyProxiedSceneDelegate {
if (@available(iOS 13, tvOS 13, *)) {
GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
id mockSharedScene = OCMClassMock([UIScene class]);
OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
GULFakeScene *mockSharedScene = [[GULFakeScene alloc] init];
mockSharedScene.delegate = realSceneDelegate;
// Proxy the scene delegate for the 1st time.
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:(UIScene *)mockSharedScene];
Class realSceneDelegateClassBefore = [realSceneDelegate class];
// Proxy the scene delegate for the 2nd time.
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:(UIScene *)mockSharedScene];
// Make sure that the class isn't changed.
XCTAssertEqualObjects([realSceneDelegate class], realSceneDelegateClassBefore);
[mockSharedScene stopMocking];
mockSharedScene = nil;
}
}
@@ -121,26 +188,29 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
NSSet *urlContexts = [NSSet set];
GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
id mockSharedScene = OCMClassMock([UIScene class]);
OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
GULFakeScene *mockSharedScene = [[GULFakeScene alloc] init];
mockSharedScene.delegate = realSceneDelegate;
id interceptor = OCMProtocolMock(@protocol(TestSceneProtocol));
OCMExpect([interceptor scene:mockSharedScene openURLContexts:urlContexts]);
GULFakeSceneDelegateInterceptor *interceptor = [[GULFakeSceneDelegateInterceptor alloc] init];
id interceptor2 = OCMProtocolMock(@protocol(TestSceneProtocol));
OCMExpect([interceptor2 scene:mockSharedScene openURLContexts:urlContexts]);
GULFakeSceneDelegateInterceptor *interceptor2 = [[GULFakeSceneDelegateInterceptor alloc] init];
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
[GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:(UIScene *)mockSharedScene];
[GULSceneDelegateSwizzler registerSceneDelegateInterceptor:interceptor];
[GULSceneDelegateSwizzler registerSceneDelegateInterceptor:interceptor2];
[realSceneDelegate scene:mockSharedScene openURLContexts:urlContexts];
OCMVerifyAll(interceptor);
OCMVerifyAll(interceptor2);
[mockSharedScene stopMocking];
mockSharedScene = nil;
[realSceneDelegate scene:(UIScene *)mockSharedScene openURLContexts:urlContexts];
__block BOOL isCalled1 = NO;
__block BOOL isCalled2 = NO;
dispatch_sync(interceptor.syncQueue, ^{
isCalled1 = interceptor.isSceneOpenURLContextsCalled;
});
dispatch_sync(interceptor2.syncQueue, ^{
isCalled2 = interceptor2.isSceneOpenURLContextsCalled;
});
XCTAssertTrue(isCalled1);
XCTAssertTrue(isCalled2);
}
}
@@ -168,12 +238,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
- (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];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that scene delegate proxy is enabled when there is neither the Firebase nor the
@@ -182,12 +250,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
- (void)testAppProxyPlistFlag_NoSceneDelegateProxyKey {
// No scene 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];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that scene delegate proxy is enabled when the Firebase plist is explicitly set to YES and
@@ -195,12 +261,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
- (void)testAppProxyPlistFlag_FirebaseEnabled {
// Set proxy enabled to YES.
NSDictionary *mainDictionary = @{kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(YES)};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that scene delegate proxy is enabled when the Google plist is explicitly set to YES and
@@ -208,12 +272,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
- (void)testAppProxyPlistFlag_GoogleEnabled {
// Set proxy enabled to YES.
NSDictionary *mainDictionary = @{kGULGoogleSceneDelegateProxyEnabledPlistKey : @(YES)};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that the scene delegate proxy is enabled when the Firebase flag has the wrong type of
@@ -221,12 +283,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
- (void)testAppProxyPlist_WrongFirebaseDisableFlagValueType {
// Set proxy enabled to "NO" - a string.
NSDictionary *mainDictionary = @{kGULFirebaseSceneDelegateProxyEnabledPlistKey : @"NO"};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that the scene delegate proxy is enabled when the Google flag has the wrong type of value
@@ -234,12 +294,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
- (void)testAppProxyPlist_WrongGoogleDisableFlagValueType {
// Set proxy enabled to "NO" - a string.
NSDictionary *mainDictionary = @{kGULGoogleSceneDelegateProxyEnabledPlistKey : @"NO"};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that the scene delegate proxy is disabled when the Firebase flag is set to NO and the
@@ -247,12 +305,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
- (void)testAppProxyPlist_FirebaseDisableFlag {
// Set proxy enabled to NO.
NSDictionary *mainDictionary = @{kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(NO)};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that the scene delegate proxy is disabled when the Google flag is set to NO and the
@@ -260,12 +316,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
- (void)testAppProxyPlist_GoogleDisableFlag {
// Set proxy enabled to NO.
NSDictionary *mainDictionary = @{kGULGoogleSceneDelegateProxyEnabledPlistKey : @(NO)};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that the scene delegate proxy is disabled when the Google flag is set to NO and the
@@ -276,12 +330,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
kGULGoogleSceneDelegateProxyEnabledPlistKey : @(NO),
kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(YES)
};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that the scene delegate proxy is disabled when the Google flag is set to NO and the
@@ -292,12 +344,10 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
kGULGoogleSceneDelegateProxyEnabledPlistKey : @(YES),
kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(NO)
};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
/** Tests that the scene delegate proxy is disabled when the Google flag is set to NO and the
@@ -308,14 +358,13 @@ API_AVAILABLE(ios(13.0), tvos(13.0))
kGULGoogleSceneDelegateProxyEnabledPlistKey : @(NO),
kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(NO)
};
id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
[[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
gSceneFakeInfoDictionary = mainDictionary;
XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
[mainBundleMock stopMocking];
mainBundleMock = nil;
gSceneFakeInfoDictionary = nil;
}
@end
#pragma clang diagnostic pop
#endif // UISCENE_SUPPORTED
@@ -12,7 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h"
+1 -9
View File
@@ -58,14 +58,7 @@ let package = Package(
targets: ["GoogleUtilities-UserDefaults"]
),
],
dependencies: [
// TODO: restore OCMock when https://github.com/erikdoe/ocmock/pull/537
// gets merged to fix Xcode 15.3 builds.
.package(
url: "https://github.com/paulb777/ocmock.git",
revision: "173955e93e6ee6999a10729ab67e4b4efdd1db6d"
),
],
dependencies: [],
targets: [
.target(
name: "GoogleUtilities-AppDelegateSwizzler",
@@ -210,7 +203,6 @@ let package = Package(
.testTarget(
name: "UtilitiesUnit",
dependencies: [
.product(name: "OCMock", package: "OCMock"),
"GoogleUtilities-AppDelegateSwizzler",
"GoogleUtilities-Environment",
"GoogleUtilities-Logger",