Bläddra i källkod

Add test for KVO in GIDGoogleUserTest

Uses bit mask to verify KVO notifications are sent.
pinlu 3 år sedan
förälder
incheckning
6543e33cba
2 ändrade filer med 96 tillägg och 9 borttagningar
  1. 0 2
      GoogleSignIn/Sources/GIDGoogleUser.m
  2. 96 7
      GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m

+ 0 - 2
GoogleSignIn/Sources/GIDGoogleUser.m

@@ -167,8 +167,6 @@ NS_ASSUME_NONNULL_BEGIN
   [self didChangeValueForKey:NSStringFromSelector(@selector(idToken))];
 }
 
-
-
 #pragma mark - NSSecureCoding
 
 + (BOOL)supportsSecureCoding {

+ 96 - 7
GoogleSignIn/Tests/Unit/GIDGoogleUserTest.m

@@ -37,11 +37,40 @@
 
 static NSString *const kNewAccessToken = @"new_access_token";
 static NSTimeInterval const kExpireTime = 442886117;
-static NSTimeInterval const kNewExpireTime = 442886123;
-static NSTimeInterval const kNewExpireTime2 = 442886124;
+static NSTimeInterval const kNewAccessTokenExpireTime = 442886123;
+static NSTimeInterval const kNewIDTokenExpireTime = 442886124;
 
 static NSTimeInterval const kTimeAccuracy = 10;
 
+// List of observed properties of the class being tested.
+static NSString *const kObservedProperties[] = {
+  @"accessToken",
+  @"refreshToken",
+  @"idToken",
+};
+static const NSUInteger kNumberOfObservedProperties =
+    sizeof(kObservedProperties) / sizeof(*kObservedProperties);
+
+// Bit position for notification change type bitmask flags.
+// Must match the list of observed properties above.
+typedef NS_ENUM(NSUInteger, ChangeType) {
+  kChangeTypeAccessTokenPrior,
+  kChangeTypeAccessToken,
+  kChangeTypeRefreshTokenPrior,
+  kChangeTypeRefreshToken,
+  kChangeTypeIDTokenPrior,
+  kChangeTypeIDToken,
+  kChangeTypeEnd  // not a real change type but an end mark for calculating |kChangeAll|
+};
+
+static const NSUInteger kChangeNone = 0u;
+static const NSUInteger kChangeAll = (1u << kChangeTypeEnd) - 1u;
+
+#if __has_feature(c_static_assert) || __has_extension(c_static_assert)
+_Static_assert(kChangeTypeEnd == (sizeof(kObservedProperties) / sizeof(*kObservedProperties)) * 2,
+               "List of observed properties must match list of change notification enums");
+#endif
+
 @interface GIDGoogleUserTest : XCTestCase
 @end
 
@@ -51,11 +80,15 @@ static NSTimeInterval const kTimeAccuracy = 10;
   
   // Fake data used to generate the expiration date of the ID token.
   NSTimeInterval _idTokenExpireTime;
+
+  // Bitmask flags for observed changes, as specified in |ChangeType|.
+  NSUInteger _changesObserved;
 }
 
 - (void)setUp {
   _accessTokenExpireTime = kAccessTokenExpiresIn;
   _idTokenExpireTime = kExpireTime;
+  _changesObserved = 0;
 }
 
 #pragma mark - Tests
@@ -111,7 +144,7 @@ static NSTimeInterval const kTimeAccuracy = 10;
 #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
 
 - (void) testUpdateAuthState {
-  GIDGoogleUser *user = [self googleUser];
+  GIDGoogleUser *user = [self observedGoogleUser];
   XCTAssertEqualObjects(user.accessToken.tokenString, kAccessToken);
   [self assertDate:user.accessToken.expirationDate equalTime:_accessTokenExpireTime];
   XCTAssertEqualObjects(user.idToken.tokenString, [self idToken]);
@@ -121,13 +154,25 @@ static NSTimeInterval const kTimeAccuracy = 10;
   [user updateAuthState:newAuthState profileData:nil];
   
   XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
-  [self assertDate:user.accessToken.expirationDate equalTime:kNewExpireTime];
+  [self assertDate:user.accessToken.expirationDate equalTime:kNewAccessTokenExpireTime];
   XCTAssertEqualObjects(user.idToken.tokenString, [self idTokenNew]);
-  [self assertDate:user.idToken.expirationDate equalTime:kNewExpireTime2];
+  [self assertDate:user.idToken.expirationDate equalTime:kNewIDTokenExpireTime];
+  XCTAssertEqual(_changesObserved, kChangeAll);
 }
 
 #pragma mark - Helpers
 
+- (GIDGoogleUser *)observedGoogleUser {
+  GIDGoogleUser *user = [self googleUser];
+  for (unsigned int i = 0; i < kNumberOfObservedProperties; ++i) {
+    [user addObserver:self
+           forKeyPath:kObservedProperties[i]
+              options:NSKeyValueObservingOptionPrior
+              context:NULL];
+  }
+  return user;
+}
+
 - (GIDGoogleUser *)googleUser {
   NSString *idToken = [self idToken];
   NSNumber *accessTokenExpiresIn =
@@ -149,7 +194,7 @@ static NSTimeInterval const kTimeAccuracy = 10;
 }
 
 - (NSString *)idTokenNew {
-  return [self idTokenWithExpireTime:kNewExpireTime2];
+  return [self idTokenWithExpireTime:kNewIDTokenExpireTime];
 }
 
 - (NSString *)idTokenWithExpireTime:(NSTimeInterval)expireTime {
@@ -157,7 +202,7 @@ static NSTimeInterval const kTimeAccuracy = 10;
 }
 
 - (OIDAuthState *)newAuthState {
-  NSNumber *expiresIn = @(kNewExpireTime - [NSDate timeIntervalSinceReferenceDate]);
+  NSNumber *expiresIn = @(kNewAccessTokenExpireTime - [NSDate timeIntervalSinceReferenceDate]);
   OIDTokenResponse *newResponse =
     [OIDTokenResponse testInstanceWithIDToken:[self idTokenNew]
                                   accessToken:kNewAccessToken
@@ -170,4 +215,48 @@ static NSTimeInterval const kTimeAccuracy = 10;
   XCTAssertEqualWithAccuracy([date timeIntervalSinceReferenceDate], time, kTimeAccuracy);
 }
 
+#pragma mark - NSKeyValueObserving
+
+- (void)observeValueForKeyPath:(NSString *)keyPath
+                      ofObject:(id)object
+                        change:(NSDictionary<NSKeyValueChangeKey,id> *)change
+                       context:(void *)context {
+  GIDGoogleUser *user = (GIDGoogleUser*)object;
+  ChangeType changeType;
+  if ([keyPath isEqualToString:@"accessToken"]) {
+    if (change[NSKeyValueChangeNotificationIsPriorKey]) {
+      XCTAssertEqualObjects(user.accessToken.tokenString, kAccessToken);
+      [self assertDate:user.accessToken.expirationDate equalTime:_accessTokenExpireTime];
+      changeType = kChangeTypeAccessTokenPrior;
+    } else {
+      XCTAssertEqualObjects(user.accessToken.tokenString, kNewAccessToken);
+      [self assertDate:user.accessToken.expirationDate equalTime:kNewAccessTokenExpireTime];
+      changeType = kChangeTypeAccessToken;
+    }
+  } else if ([keyPath isEqualToString:@"refreshToken"]) {
+    if (change[NSKeyValueChangeNotificationIsPriorKey]) {
+      changeType = kChangeTypeRefreshTokenPrior;
+    } else {
+      changeType = kChangeTypeRefreshToken;
+    }
+  } else if ([keyPath isEqualToString:@"idToken"]) {
+    if (change[NSKeyValueChangeNotificationIsPriorKey]) {
+      XCTAssertEqualObjects(user.idToken.tokenString, [self idToken]);
+      [self assertDate:user.idToken.expirationDate equalTime:_idTokenExpireTime];
+      changeType = kChangeTypeIDTokenPrior;
+    } else {
+      XCTAssertEqualObjects(user.idToken.tokenString, [self idTokenNew]);
+      [self assertDate:user.idToken.expirationDate equalTime:kNewIDTokenExpireTime];
+      changeType = kChangeTypeIDToken;
+    }
+  } else {
+    XCTFail(@"unexpected keyPath");
+    return;  // so compiler knows |changeType| is always assigned
+  }
+  
+  NSInteger changeMask = 1 << changeType;
+  XCTAssertFalse(_changesObserved & changeMask);  // each change type should only fire once
+  _changesObserved |= changeMask;
+}
+
 @end