Back to tech

Flutter で Firebase Auth With Google の Unit Test を書く

4 min read
Table of Contents

お久しぶりです。みやかわです。

お仕事で古のJSを使ってプログラムを作っていいます。

プライベートでは Flutter を使用しています。

さて、今回は Flutter で Firebase Auth With Google の Unit Test(ユニットテスト) を書いてみた内容についてまとめます。

というのも、Firebase Auth With Google の Unit Test を書くために一ヶ月以上浪費した経緯があります。

かなり沼りました。

次回沼らないようにするために記事にしていきます。

実装環境

  • OS macOS Monterey 12.0.1(21A559)

  • Flutter Flutter 2.10.3

    Dart 2.16.1 • DevTools 2.9.2

Unit Test を書く

使用するパッケージ

mockito: ^5.1.0

Unit Test する Class

以下の Google Auth を行うクラスを書いたとします。

この Class を使うには、GoogleSignInFirebaseAuth を渡してあげる必要があります。(理由は後述)

class GoogleAuthApi {
  GoogleAuthApi({
    required this.googleSignIn,
    required this.firebaseAuth,
  });

  final GoogleSignIn googleSignIn;
  final FirebaseAuth firebaseAuth;

  Future<UserCredential?> sigInWithGoogle() async {
    UserCredential? _response;

    try {
      final _googleUser = await googleSignIn.signIn();
      final _googleAuth = await _googleUser?.authentication;

      if (_googleAuth != null) {
        final _credential = GoogleAuthProvider.credential(
          accessToken: _googleAuth.accessToken,
          idToken: _googleAuth.idToken,
        );

        _response = await firebaseAuth.signInWithCredential(_credential);
      }
    } on PlatformException catch (e) {
      debugPrint('PlatformException: $e');
    } on FirebaseAuthException catch (e) {
      debugPrint('FirebaseAuthException: $e');
    }
    return _response;
  }

  Future sigOutWithGoogle() async {
    bool _isSigOutWithGoogle = false;

    try {
      await firebaseAuth.signOut();
      _isSigOutWithGoogle = true;
    } on PlatformException catch (e) {
      debugPrint('PlatformException: $e');
    } on FirebaseAuthException catch (e) {
      debugPrint('FirebaseAuthException: $e');
    }

    return _isSigOutWithGoogle;
  }
}

このクラスには以下の関数があります。

  • sigInWithGoogle() Google サインインする
  • sigOutWithGoogle() Google サインアウトする

この関数をUnit Test がしたいとします。

簡単にUnitTestするために

Classの説明で、このClassを使うためには GoogleSignInFirebaseAuth を渡してあげる必要があると書きました。

これするのが肝で、これをやらないとかなりUnit Testを書くのがめんどくさくなります。

※ Unit Test を書くときに Firebase まわりで色々とエラーになる。 ※ これでかなり時間を溶かしました。 ※ 例えば、 Firebase.initializeApp(); のMockを書くのがめんどいです。

GoogleSignInFirebaseAuth は Mock で注入することで簡単に Unit Test をすることができます。

Unit Test をする

色々書きましたが、本題の Unit Test のコードを書きます。

Unit Test するための main は以下のようになっています。

// mock の生成
@GenerateMocks([
  GoogleSignIn,
  FirebaseAuth,
  GoogleSignInAccount,
  GoogleSignInAuthentication,
  UserCredential,
])
void main() {
  // ここに Unit Test をつらつら書いていく感じ
}

サインインの成功

test('Googleのサインインに成功する', () async {
      final MockGoogleSignIn mockGoogleSignIn = MockGoogleSignIn();
      final MockFirebaseAuth mockFirebaseAuth = MockFirebaseAuth();
      final MockGoogleSignInAccount mockGoogleSignInAccount =
          MockGoogleSignInAccount();
      final MockGoogleSignInAuthentication mockGoogleSignInAuthentication =
          MockGoogleSignInAuthentication();
      final MockUserCredential mockUserCredential = MockUserCredential();

            // set mock
      when(mockGoogleSignInAuthentication.idToken).thenReturn('idToken');
      when(mockGoogleSignInAuthentication.accessToken).thenReturn('accessToke');
      when(mockGoogleSignInAccount.authentication)
          .thenAnswer((_) => Future.value(mockGoogleSignInAuthentication));
      when(mockGoogleSignIn.signIn())
          .thenAnswer((_) => Future.value(mockGoogleSignInAccount));
      when(mockFirebaseAuth.signInWithCredential(any))
          .thenAnswer((_) => Future.value(mockUserCredential));

      final GoogleAuthApi googleAuthApi = GoogleAuthApi(
        googleSignIn: mockGoogleSignIn,
        firebaseAuth: mockFirebaseAuth,
      );
      final response = await googleAuthApi.sigInWithGoogle();

      expect(response, mockUserCredential);
    });

サインインの失敗

test('Googleのサインインに失敗する(PlatformException)', () async {
      final MockGoogleSignIn mockGoogleSignIn = MockGoogleSignIn();
      final MockFirebaseAuth mockFirebaseAuth = MockFirebaseAuth();
      final MockGoogleSignInAccount mockGoogleSignInAccount =
          MockGoogleSignInAccount();
      final MockGoogleSignInAuthentication mockGoogleSignInAuthentication =
          MockGoogleSignInAuthentication();

            // set mock
      when(mockGoogleSignInAuthentication.idToken).thenReturn('id');
      when(mockGoogleSignInAuthentication.accessToken).thenReturn('token');
      when(mockGoogleSignInAccount.authentication)
          .thenAnswer((_) => Future.value(mockGoogleSignInAuthentication));
      when(mockGoogleSignIn.signIn())
          .thenAnswer((_) => Future.value(mockGoogleSignInAccount));
      when(mockFirebaseAuth.signInWithCredential(any))
          .thenThrow(PlatformException(code: 'err'));

      final GoogleAuthApi googleAuthApi = GoogleAuthApi(
        googleSignIn: mockGoogleSignIn,
        firebaseAuth: mockFirebaseAuth,
      );
      final response = await googleAuthApi.sigInWithGoogle();

      expect(response, null);
    });

    test('Googleのサインインに失敗する(FirebaseAuthException)', () async {
      final MockGoogleSignIn mockGoogleSignIn = MockGoogleSignIn();
      final MockFirebaseAuth mockFirebaseAuth = MockFirebaseAuth();
      final MockGoogleSignInAccount mockGoogleSignInAccount =
          MockGoogleSignInAccount();
      final MockGoogleSignInAuthentication mockGoogleSignInAuthentication =
          MockGoogleSignInAuthentication();

      when(mockGoogleSignInAuthentication.idToken).thenReturn('id');
      when(mockGoogleSignInAuthentication.accessToken).thenReturn('token');
      when(mockGoogleSignInAccount.authentication)
          .thenAnswer((_) => Future.value(mockGoogleSignInAuthentication));
      when(mockGoogleSignIn.signIn())
          .thenAnswer((_) => Future.value(mockGoogleSignInAccount));
      when(mockFirebaseAuth.signInWithCredential(any))
          .thenThrow(FirebaseAuthException(code: 'err'));

      final GoogleAuthApi googleAuthApi = GoogleAuthApi(
        googleSignIn: mockGoogleSignIn,
        firebaseAuth: mockFirebaseAuth,
      );
      final response = await googleAuthApi.sigInWithGoogle();

      expect(response, null);
    });

サインアウトの成功

test('Googleのサインアウトに成功する', () async {
      final MockGoogleSignIn mockGoogleSignIn = MockGoogleSignIn();
      final MockFirebaseAuth mockFirebaseAuth = MockFirebaseAuth();

      // set mock
      when(mockFirebaseAuth.signOut()).thenAnswer((_) => Future.value());

      final GoogleAuthApi googleAuthApi = GoogleAuthApi(
        googleSignIn: mockGoogleSignIn,
        firebaseAuth: mockFirebaseAuth,
      );
      final response = await googleAuthApi.sigOutWithGoogle();
      expect(response, true);
    });

サインアウトの失敗

test('Googleのサインアウトに失敗する(PlatformException)', () async {
      final MockGoogleSignIn mockGoogleSignIn = MockGoogleSignIn();
      final MockFirebaseAuth mockFirebaseAuth = MockFirebaseAuth();

      // set mock
      when(mockFirebaseAuth.signOut())
          .thenThrow(PlatformException(code: 'err'));

      final GoogleAuthApi googleAuthApi = GoogleAuthApi(
        googleSignIn: mockGoogleSignIn,
        firebaseAuth: mockFirebaseAuth,
      );
      final response = await googleAuthApi.sigOutWithGoogle();
      expect(response, false);
    });

    test('Googleのサインアウトに失敗する(PlatformException)', () async {
      final MockGoogleSignIn mockGoogleSignIn = MockGoogleSignIn();
      final MockFirebaseAuth mockFirebaseAuth = MockFirebaseAuth();

      // set mock
      when(mockFirebaseAuth.signOut())
          .thenThrow(FirebaseAuthException(code: 'err'));

      final GoogleAuthApi googleAuthApi = GoogleAuthApi(
        googleSignIn: mockGoogleSignIn,
        firebaseAuth: mockFirebaseAuth,
      );
      final response = await googleAuthApi.sigOutWithGoogle();
      expect(response, false);
    });
  });

その他

firebase auth の有名な Mock として firebase_auth_mocks がある。

このパッケージをインストールした状態で、mokit で FirebaseAuth の mock を自動生成すると、命名規則とかぶる。

そのため、 when() で mock を注入するときに誤って firebase_auth_mocks を when() にしてしまい、怒られることがあるので注意。