From e4035cf5d3796f23a9b1c31cc1c50106a3e12805 Mon Sep 17 00:00:00 2001 From: Nick Cooke <36927374+ncooke3@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:49:15 -0400 Subject: [PATCH] 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 --- .ruby-version | 2 +- GoogleUtilities.podspec | 1 - .../Environment/GULAppEnvironmentUtil.m | 16 +- .../Environment/GULAppEnvironmentUtilTest.m | 35 +- .../Environment/GULKeychainStorageTests.m | 183 ++++-- .../Tests/Unit/Logger/GULLoggerTest.m | 1 - .../Tests/Unit/Network/GULNetworkTest.m | 41 +- .../URLSession/FIRURLSessionOCMockStub.h | 35 -- .../URLSession/FIRURLSessionOCMockStub.m | 65 --- .../Swizzler/GULAppDelegateSwizzlerTest.m | 525 ++++++++++-------- .../Swizzler/GULSceneDelegateSwizzlerTest.m | 201 ++++--- .../Unit/UserDefaults/GULUserDefaultsTests.m | 1 - Package.swift | 10 +- 13 files changed, 601 insertions(+), 515 deletions(-) delete mode 100644 GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.h delete mode 100644 GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.m diff --git a/.ruby-version b/.ruby-version index fa376ed..47b322c 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.7 +3.4.1 diff --git a/GoogleUtilities.podspec b/GoogleUtilities.podspec index bf1ba3f..262858b 100644 --- a/GoogleUtilities.podspec +++ b/GoogleUtilities.podspec @@ -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| diff --git a/GoogleUtilities/Environment/GULAppEnvironmentUtil.m b/GoogleUtilities/Environment/GULAppEnvironmentUtil.m index 8170c6a..5823d24 100644 --- a/GoogleUtilities/Environment/GULAppEnvironmentUtil.m +++ b/GoogleUtilities/Environment/GULAppEnvironmentUtil.m @@ -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 { diff --git a/GoogleUtilities/Tests/Unit/Environment/GULAppEnvironmentUtilTest.m b/GoogleUtilities/Tests/Unit/Environment/GULAppEnvironmentUtilTest.m index 8f00512..2a576c7 100644 --- a/GoogleUtilities/Tests/Unit/Environment/GULAppEnvironmentUtilTest.m +++ b/GoogleUtilities/Tests/Unit/Environment/GULAppEnvironmentUtilTest.m @@ -13,7 +13,6 @@ // limitations under the License. #import -#import #import #if __has_include() @@ -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 } diff --git a/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m b/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m index 05d0af8..27c19e6 100644 --- a/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m +++ b/GoogleUtilities/Tests/Unit/Environment/GULKeychainStorageTests.m @@ -28,7 +28,6 @@ #import -#import #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)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)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 _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 _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 diff --git a/GoogleUtilities/Tests/Unit/Logger/GULLoggerTest.m b/GoogleUtilities/Tests/Unit/Logger/GULLoggerTest.m index 9403564..a2efa0a 100644 --- a/GoogleUtilities/Tests/Unit/Logger/GULLoggerTest.m +++ b/GoogleUtilities/Tests/Unit/Logger/GULLoggerTest.m @@ -15,7 +15,6 @@ #ifdef DEBUG // The tests depend upon library methods only built with #ifdef DEBUG -#import #import #import "GoogleUtilities/Logger/Public/GoogleUtilities/GULLogger.h" diff --git a/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m b/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m index 58e9f3d..372a45e 100644 --- a/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m +++ b/GoogleUtilities/Tests/Unit/Network/GULNetworkTest.m @@ -14,7 +14,6 @@ #import "GoogleUtilities/Tests/Unit/Network/third_party/GTMHTTPServer.h" -#import #import #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 { diff --git a/GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.h b/GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.h deleted file mode 100644 index 2895b03..0000000 --- a/GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.h +++ /dev/null @@ -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 - -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 diff --git a/GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.m b/GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.m deleted file mode 100644 index f88b811..0000000 --- a/GoogleUtilities/Tests/Unit/Shared/URLSession/FIRURLSessionOCMockStub.m +++ /dev/null @@ -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 - -@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 diff --git a/GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m b/GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m index 1933987..d7d059c 100644 --- a/GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m +++ b/GoogleUtilities/Tests/Unit/Swizzler/GULAppDelegateSwizzlerTest.m @@ -15,7 +15,6 @@ #import "GoogleUtilities/AppDelegateSwizzler/Internal/GULAppDelegateSwizzler_Private.h" #import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULAppDelegateSwizzler.h" -#import #import #import @@ -240,28 +239,166 @@ static BOOL gRespondsToHandleBackgroundSession; @end +@interface GULFakeApplication : NSObject +@property(nonatomic, strong) id 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 +@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 *)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> *__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)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); diff --git a/GoogleUtilities/Tests/Unit/Swizzler/GULSceneDelegateSwizzlerTest.m b/GoogleUtilities/Tests/Unit/Swizzler/GULSceneDelegateSwizzlerTest.m index 52630ac..9c8c2ea 100644 --- a/GoogleUtilities/Tests/Unit/Swizzler/GULSceneDelegateSwizzlerTest.m +++ b/GoogleUtilities/Tests/Unit/Swizzler/GULSceneDelegateSwizzlerTest.m @@ -15,7 +15,6 @@ #import "GoogleUtilities/AppDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h" #import "GoogleUtilities/AppDelegateSwizzler/Public/GoogleUtilities/GULSceneDelegateSwizzler.h" -#import #import #import @@ -35,6 +34,8 @@ static NSString *const kGULGoogleSceneDelegateProxyEnabledPlistKey = #if UISCENE_SUPPORTED +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" @protocol TestSceneProtocol @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 +@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 *)URLContexts { + dispatch_sync(_syncQueue, ^{ + self->_isSceneOpenURLContextsCalled = YES; + }); +} +@end + +API_AVAILABLE(ios(13.0), tvos(13.0)) +@interface GULFakeScene : NSObject +@property(nonatomic, weak) id 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 diff --git a/GoogleUtilities/Tests/Unit/UserDefaults/GULUserDefaultsTests.m b/GoogleUtilities/Tests/Unit/UserDefaults/GULUserDefaultsTests.m index ee55926..f1077a0 100644 --- a/GoogleUtilities/Tests/Unit/UserDefaults/GULUserDefaultsTests.m +++ b/GoogleUtilities/Tests/Unit/UserDefaults/GULUserDefaultsTests.m @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import #import #import "GoogleUtilities/Network/Public/GoogleUtilities/GULMutableDictionary.h" diff --git a/Package.swift b/Package.swift index fdc1ddf..3be2a75 100644 --- a/Package.swift +++ b/Package.swift @@ -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",