| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:google_sign_in/google_sign_in.dart'; |
| import 'package:google_sign_in_platform_interface/google_sign_in_platform_interface.dart'; |
| import 'package:mockito/annotations.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'google_sign_in_test.mocks.dart'; |
| |
| /// Verify that [GoogleSignInAccount] can be mocked even though it's unused |
| // ignore: must_be_immutable |
| class MockGoogleSignInAccount extends Mock implements GoogleSignInAccount {} |
| |
| @GenerateMocks(<Type>[GoogleSignInPlatform]) |
| void main() { |
| late MockGoogleSignInPlatform mockPlatform; |
| |
| group('GoogleSignIn', () { |
| final GoogleSignInUserData kDefaultUser = GoogleSignInUserData( |
| email: 'john.doe@gmail.com', |
| id: '8162538176523816253123', |
| photoUrl: 'https://lh5.googleusercontent.com/photo.jpg', |
| displayName: 'John Doe', |
| serverAuthCode: '789'); |
| |
| setUp(() { |
| mockPlatform = MockGoogleSignInPlatform(); |
| when(mockPlatform.isMock).thenReturn(true); |
| when(mockPlatform.signInSilently()) |
| .thenAnswer((Invocation _) async => kDefaultUser); |
| when(mockPlatform.signIn()) |
| .thenAnswer((Invocation _) async => kDefaultUser); |
| |
| GoogleSignInPlatform.instance = mockPlatform; |
| }); |
| |
| test('signInSilently', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| |
| await googleSignIn.signInSilently(); |
| |
| expect(googleSignIn.currentUser, isNotNull); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signInSilently()); |
| }); |
| |
| test('signIn', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| |
| await googleSignIn.signIn(); |
| |
| expect(googleSignIn.currentUser, isNotNull); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signIn()); |
| }); |
| |
| test('signIn prioritize clientId parameter when available', () async { |
| const String fakeClientId = 'fakeClientId'; |
| final GoogleSignIn googleSignIn = GoogleSignIn(clientId: fakeClientId); |
| |
| await googleSignIn.signIn(); |
| |
| _verifyInit(mockPlatform, clientId: fakeClientId); |
| verify(mockPlatform.signIn()); |
| }); |
| |
| test('signOut', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| |
| await googleSignIn.signOut(); |
| |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signOut()); |
| }); |
| |
| test('disconnect; null response', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| |
| await googleSignIn.disconnect(); |
| |
| expect(googleSignIn.currentUser, isNull); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.disconnect()); |
| }); |
| |
| test('isSignedIn', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| when(mockPlatform.isSignedIn()).thenAnswer((Invocation _) async => true); |
| |
| final bool result = await googleSignIn.isSignedIn(); |
| |
| expect(result, isTrue); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.isSignedIn()); |
| }); |
| |
| test('signIn works even if a previous call throws error in other zone', |
| () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| |
| when(mockPlatform.signInSilently()).thenThrow(Exception('Not a user')); |
| await runZonedGuarded(() async { |
| expect(await googleSignIn.signInSilently(), isNull); |
| }, (Object e, StackTrace st) {}); |
| expect(await googleSignIn.signIn(), isNotNull); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signInSilently()); |
| verify(mockPlatform.signIn()); |
| }); |
| |
| test('concurrent calls of the same method trigger sign in once', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| final List<Future<GoogleSignInAccount?>> futures = |
| <Future<GoogleSignInAccount?>>[ |
| googleSignIn.signInSilently(), |
| googleSignIn.signInSilently(), |
| ]; |
| |
| expect(futures.first, isNot(futures.last), |
| reason: 'Must return new Future'); |
| |
| final List<GoogleSignInAccount?> users = await Future.wait(futures); |
| |
| expect(googleSignIn.currentUser, isNotNull); |
| expect(users, <GoogleSignInAccount?>[ |
| googleSignIn.currentUser, |
| googleSignIn.currentUser |
| ]); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signInSilently()).called(1); |
| }); |
| |
| test('can sign in after previously failed attempt', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| when(mockPlatform.signInSilently()).thenThrow(Exception('Not a user')); |
| |
| expect(await googleSignIn.signInSilently(), isNull); |
| expect(await googleSignIn.signIn(), isNotNull); |
| |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signInSilently()); |
| verify(mockPlatform.signIn()); |
| }); |
| |
| test('concurrent calls of different signIn methods', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| final List<Future<GoogleSignInAccount?>> futures = |
| <Future<GoogleSignInAccount?>>[ |
| googleSignIn.signInSilently(), |
| googleSignIn.signIn(), |
| ]; |
| expect(futures.first, isNot(futures.last)); |
| |
| final List<GoogleSignInAccount?> users = await Future.wait(futures); |
| |
| expect(users.first, users.last, reason: 'Must return the same user'); |
| expect(googleSignIn.currentUser, users.last); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signInSilently()); |
| verifyNever(mockPlatform.signIn()); |
| }); |
| |
| test('can sign in after aborted flow', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| |
| when(mockPlatform.signIn()).thenAnswer((Invocation _) async => null); |
| expect(await googleSignIn.signIn(), isNull); |
| |
| when(mockPlatform.signIn()) |
| .thenAnswer((Invocation _) async => kDefaultUser); |
| expect(await googleSignIn.signIn(), isNotNull); |
| }); |
| |
| test('signOut/disconnect methods always trigger native calls', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| final List<Future<GoogleSignInAccount?>> futures = |
| <Future<GoogleSignInAccount?>>[ |
| googleSignIn.signOut(), |
| googleSignIn.signOut(), |
| googleSignIn.disconnect(), |
| googleSignIn.disconnect(), |
| ]; |
| |
| await Future.wait(futures); |
| |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signOut()).called(2); |
| verify(mockPlatform.disconnect()).called(2); |
| }); |
| |
| test('queue of many concurrent calls', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| final List<Future<GoogleSignInAccount?>> futures = |
| <Future<GoogleSignInAccount?>>[ |
| googleSignIn.signInSilently(), |
| googleSignIn.signOut(), |
| googleSignIn.signIn(), |
| googleSignIn.disconnect(), |
| ]; |
| |
| await Future.wait(futures); |
| |
| _verifyInit(mockPlatform); |
| verifyInOrder(<Object>[ |
| mockPlatform.signInSilently(), |
| mockPlatform.signOut(), |
| mockPlatform.signIn(), |
| mockPlatform.disconnect(), |
| ]); |
| }); |
| |
| test('signInSilently suppresses errors by default', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| when(mockPlatform.signInSilently()).thenThrow(Exception('I am an error')); |
| expect(await googleSignIn.signInSilently(), isNull); // should not throw |
| }); |
| |
| test('signInSilently forwards exceptions', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| when(mockPlatform.signInSilently()).thenThrow(Exception('I am an error')); |
| expect(googleSignIn.signInSilently(suppressErrors: false), |
| throwsA(isInstanceOf<Exception>())); |
| }); |
| |
| test('signInSilently allows re-authentication to be requested', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| await googleSignIn.signInSilently(); |
| expect(googleSignIn.currentUser, isNotNull); |
| |
| await googleSignIn.signInSilently(reAuthenticate: true); |
| |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signInSilently()).called(2); |
| }); |
| |
| test('can sign in after init failed before', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| |
| when(mockPlatform.init()).thenThrow(Exception('First init fails')); |
| expect(googleSignIn.signIn(), throwsA(isInstanceOf<Exception>())); |
| |
| when(mockPlatform.init()).thenAnswer((Invocation _) async {}); |
| expect(await googleSignIn.signIn(), isNotNull); |
| }); |
| |
| test('created with standard factory uses correct options', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn.standard(); |
| |
| await googleSignIn.signInSilently(); |
| expect(googleSignIn.currentUser, isNotNull); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signInSilently()); |
| }); |
| |
| test('created with defaultGamesSignIn factory uses correct options', |
| () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn.games(); |
| |
| await googleSignIn.signInSilently(); |
| expect(googleSignIn.currentUser, isNotNull); |
| _verifyInit(mockPlatform, signInOption: SignInOption.games); |
| verify(mockPlatform.signInSilently()); |
| }); |
| |
| test('authentication', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| when(mockPlatform.getTokens( |
| email: anyNamed('email'), |
| shouldRecoverAuth: anyNamed('shouldRecoverAuth'))) |
| .thenAnswer((Invocation _) async => GoogleSignInTokenData( |
| idToken: '123', |
| accessToken: '456', |
| serverAuthCode: '789', |
| )); |
| |
| await googleSignIn.signIn(); |
| |
| final GoogleSignInAccount user = googleSignIn.currentUser!; |
| final GoogleSignInAuthentication auth = await user.authentication; |
| |
| expect(auth.accessToken, '456'); |
| expect(auth.idToken, '123'); |
| verify(mockPlatform.getTokens( |
| email: 'john.doe@gmail.com', shouldRecoverAuth: true)); |
| }); |
| |
| test('requestScopes returns true once new scope is granted', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| when(mockPlatform.requestScopes(any)) |
| .thenAnswer((Invocation _) async => true); |
| |
| await googleSignIn.signIn(); |
| final bool result = |
| await googleSignIn.requestScopes(<String>['testScope']); |
| |
| expect(result, isTrue); |
| _verifyInit(mockPlatform); |
| verify(mockPlatform.signIn()); |
| verify(mockPlatform.requestScopes(<String>['testScope'])); |
| }); |
| |
| test('user starts as null', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| expect(googleSignIn.currentUser, isNull); |
| }); |
| |
| test('can sign in and sign out', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| await googleSignIn.signIn(); |
| |
| final GoogleSignInAccount user = googleSignIn.currentUser!; |
| |
| expect(user.displayName, equals(kDefaultUser.displayName)); |
| expect(user.email, equals(kDefaultUser.email)); |
| expect(user.id, equals(kDefaultUser.id)); |
| expect(user.photoUrl, equals(kDefaultUser.photoUrl)); |
| expect(user.serverAuthCode, equals(kDefaultUser.serverAuthCode)); |
| |
| await googleSignIn.disconnect(); |
| expect(googleSignIn.currentUser, isNull); |
| }); |
| |
| test('disconnect when signout already succeeds', () async { |
| final GoogleSignIn googleSignIn = GoogleSignIn(); |
| await googleSignIn.disconnect(); |
| expect(googleSignIn.currentUser, isNull); |
| }); |
| }); |
| } |
| |
| void _verifyInit( |
| MockGoogleSignInPlatform mockSignIn, { |
| SignInOption signInOption = SignInOption.standard, |
| String? clientId, |
| }) { |
| verify(mockSignIn.init( |
| signInOption: signInOption, |
| scopes: <String>[], |
| hostedDomain: null, |
| clientId: clientId, |
| )); |
| } |