-
Notifications
You must be signed in to change notification settings - Fork 3.5k
[google_sign_in] Remove use of OCMock #10290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[google_sign_in] Remove use of OCMock #10290
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This pull request is a significant and well-executed refactoring to remove the OCMock dependency from the iOS Google Sign-In plugin tests. By introducing a protocol-based abstraction layer for the Google Sign-In SDK, you've greatly improved the testability of the native code, which is a crucial step for the planned migration to Swift. The use of wrapper classes for production and hand-crafted fakes for testing is a clean and effective pattern. The changes are extensive but logical and consistent throughout the codebase. I have a few suggestions to further improve the code's robustness and adherence to Objective-C best practices.
packages/google_sign_in/google_sign_in_ios/darwin/Tests/GoogleSignInTests.m
Show resolved
Hide resolved
| @property(strong, nonatomic) NSString *email; | ||
| @property(strong, nonatomic) NSString *name; | ||
| /// A URL to return from imageURLWithDimension:. | ||
| @property(strong, nonatomic) NSURL *imageURL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Properties for objects that have mutable counterparts (like NSString, NSArray, NSDictionary, NSURL, NSDate) should be declared with copy instead of strong. This prevents the internal state of the object from being changed unexpectedly by another object that holds a reference to a mutable version. This is a standard Objective-C best practice.
This applies to email, name, and imageURL here, and also to properties in other test fake classes:
TestToken:tokenString,expirationDateTestSignInResult:serverAuthCodeTestGoogleUser:userID,grantedScopes,requestedScopes
| @property(strong, nonatomic) NSString *email; | |
| @property(strong, nonatomic) NSString *name; | |
| /// A URL to return from imageURLWithDimension:. | |
| @property(strong, nonatomic) NSURL *imageURL; | |
| @property(copy, nonatomic) NSString *email; | |
| @property(copy, nonatomic) NSString *name; | |
| /// A URL to return from imageURLWithDimension:. | |
| @property(copy, nonatomic) NSURL *imageURL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately the compiler gets angry if you re-declare a public readonly property as copy, even if it's specifically being changed to readwrite. That seems like a bug, but we're stuck with it.
I don't want to make the protocols use copy because they are duplicated exactly from the SDK, in anticipation of the switch to Swift class extensions to add protocol conformance.
(I could instead make these ivars, add manual implementations of the read-only properties, and declare an initWith... to set them all, but that's a bunch more test code to fix something that should not actually matter in tests.)
...gle_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/FLTGoogleSignInPlugin.m
Outdated
Show resolved
Hide resolved
| /// to GIDSignIn. | ||
| @protocol FSIGIDSignIn <NSObject> | ||
|
|
||
| @property(nonatomic, nullable) GIDConfiguration *configuration; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason this uses GIDConfiguration directly, instead of introducing an FSIGIDConfiguration protocol like everything else, is that for whatever reason the SDK exposes this as a data class with public initializers, unlike the other classes that look data-class-y (GIDSignInResult, GIDToken, etc.) which don't have public initializers.
Since we can make instances of this class with arbitrary data in tests, we don't need to wrap it to allow for that.
| // https://github.com/flutter/flutter/issues/104117 | ||
| return [self topViewControllerFromViewController:[UIApplication sharedApplication] | ||
| .keyWindow.rootViewController]; | ||
| #pragma clang diagnostic pop |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went ahead and moved this code into the view provider abstraction, since that's where the non-deprecated replacement codepath will live, it's what I have already done in at least one other plugin where I introduced a view provider, and it means we could write tests for code that calls this without hitting issues with trying to use the actual UIApplication in a unit test.
| OCMStub(self.mockPluginRegistrar.messenger).andReturn(self.mockBinaryMessenger); | ||
| self.plugin = [[FLTGoogleSignInPlugin alloc] initWithSignIn:mockSignIn | ||
| registrar:self.mockPluginRegistrar]; | ||
| [FLTGoogleSignInPlugin registerWithRegistrar:self.mockPluginRegistrar]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no idea why this line was here; it was making an instance without any test DI that we were never using in tests. That's generally not something we want to do in tests, so I removed it while I was adjusting the DI around it.
| XCTAssertNil(error); | ||
| // No configuration should be set, allowing the SDK to use its default behavior | ||
| // (which is to load configuration information from Info.plist). | ||
| XCTAssertNil(self.fakeSignIn.configuration); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the only place where an existing test was changed in a way that wasn't just adapting the OCMock logic to the new approach. That's because this looked like an actual test, but it wasn't: the OCMExpect call was never actually triggered, so none of the XCTAsserts were being executed. Reading from the Info.plist is logic that is in the SDK, not in the plugin. That's why line 186 was making a different plugin instance that didn't inject a mock Google Sign In instance... but that also meant that an expectation on self.mockSignIn never fired.
So basically this test was just testing that the SDK didn't crash, not that it actually had the expected behavior. I considered fixing this to read the values from the actual config, but having a plugin unit test that is asserting SDK behavior rather than plugin behavior is weird, so I changed it to instead assert the plugin behavior we want, which is to not set a config so that the SDK uses the one it creates by default from the Info.plist.
|
|
||
| #pragma mark - addScopes | ||
|
|
||
| - (void)testRequestScopesPassesScopes { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly I didn't do test backfill in this PR, but I noticed we had a lot of tests of error conditions for addScopes:... but nothing that tested that we actually requested the right scopes, so added that quickly while I was here.
...gle_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/FLTGoogleSignInPlugin.m
Show resolved
Hide resolved
...gle_sign_in_ios/darwin/google_sign_in_ios/Sources/google_sign_in_ios/FLTGoogleSignInPlugin.m
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to make the new header files private, or are they intended to be public so people can fake GID objects themselves?
| #pragma clang diagnostic ignored "-Wdeprecated-declarations" | ||
| // TODO(stuartmorgan) Provide a non-deprecated codepath. See | ||
| // https://github.com/flutter/flutter/issues/104117 | ||
| return UIApplication.sharedApplication.keyWindow.rootViewController; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we added a view controller getter on the iOS registrar. Because that change hasn't landed in stable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, it's not in stable yet.
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| // TODO(stuarmorgan): Replace these with protocol extensions when migrating to Swift. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe I'm reading this wrong but I thought in objective-c protocols can't have default implementations? For example if FSIViewProvider needs to be exposed to objective-c then each conformer has to implement all the required properties and methods manually (instead of getting them for free via protocol extensions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if
FSIViewProviderneeds to be exposed to objective-c
It doesn't, it's an internal implementation detail. It just doesn't look that way because of the issues mentioned in my other comment. When we migrate to Swift we'll make all of it Swift. There is nothing in google_sign_in's native code that we actually need to expose publicly.
We've had a bunch of issues in the past with trying to make Obj-C headers private and how that interacts with modules and umbrella headers. In general our solution has been to just ignore it; in practice essentially nobody uses any of the native headers anyway, so it's largely moot. And the problem will go away as we migrate to Swift, so the incentive to put more effort into trying to massage the built into working with private headers is low. |
Ah, and to helpfully remind me what this looks like, CI is failing because I didn't expose one of these internal headers enough:
|
flutter/packages@1a7075b...3d926aa 2025-11-04 49699333+dependabot[bot]@users.noreply.github.com [dependabot]: Bump com.squareup.okhttp3:okhttp from 5.1.0 to 5.3.0 in /packages/espresso/android (flutter/packages#10348) 2025-11-04 engine-flutter-autoroll@skia.org Roll Flutter from 6f8abdd to 027f2e4 (26 revisions) (flutter/packages#10335) 2025-11-03 stuartmorgan@google.com [google_sign_in] Remove use of OCMock (flutter/packages#10290) 2025-11-03 stuartmorgan@google.com [interactive_media_ads] Pin iOS dependency maximum (flutter/packages#10349) 2025-10-30 magder@google.com [webview_flutter_wkwebview] Remove specialization of 'map' to fix Swift warning (flutter/packages#9810) 2025-10-30 engine-flutter-autoroll@skia.org Roll Flutter from df72035 to 6f8abdd (16 revisions) (flutter/packages#10327) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
* 'main' of https://github.com/CaoGiaHieu-dev/packages: Update packages/go_router/CHANGELOG.md [tool] Remove use of FETCH_HEAD (flutter#10357) Roll Flutter from 027f2e410241 to e5d5c01850f2 (73 revisions) (flutter#10362) [camera_platform_interface] Adds support for video stabilization to camera_platform_interface (flutter#10337) [google_maps_flutter] Raise `MapUsedAfterWidgetDisposedError` when map controller used after map disposed (flutter#9242) [pigeon] Replace containsKey with contains in Kotlin generator (flutter#10274) [video_player] Remove `package` in example `AndroidManifest.xml` file (flutter#10245) [flutter_svg] Fixes typo of `allowDrawingOutsideViewBox` in doc comments. (flutter#10256) [in_app_purchase] Remove use of Pigeon's Dart test generator (flutter#10328) [dependabot]: Bump com.squareup.okhttp3:okhttp from 5.1.0 to 5.3.0 in /packages/espresso/android (flutter#10348) Roll Flutter from 6f8abdd77820 to 027f2e410241 (26 revisions) (flutter#10335) [google_sign_in] Remove use of OCMock (flutter#10290) [interactive_media_ads] Pin iOS dependency maximum (flutter#10349)
Wraps Google Sign In SDK classes with protocols, so that we can inject fakes that implement the protocols instead of using OCMock to mock the actual classes. This will allow conversion of the tests to Swift, in preparation for migrating the entire plugin to Swift.
For now the protocols are implemented in production with passthrough wrappers, so this adds a fair amount of boilerplate code to the plugin, but almost all of it will be removed when the plugin implementation is converted to Swift since we will be able to use extensions to make the SDK classes conform to the protocol instead of maintaining wrappers.
Unblocks flutter/flutter#119103
Pre-Review Checklist
[shared_preferences]pubspec.yamlwith an appropriate new version according to the pub versioning philosophy, or I have commented below to indicate which version change exemption this PR falls under1.CHANGELOG.mdto add a description of the change, following repository CHANGELOG style, or I have commented below to indicate which CHANGELOG exemption this PR falls under1.///).Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2 ↩3