お久しぶりです。みやかわです。
お仕事で古の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.3Dart 2.16.1 • DevTools 2.9.2
Unit Test を書く
使用するパッケージ
mockito: ^5.1.0
Unit Test する Class
以下の Google Auth を行うクラスを書いたとします。
この Class を使うには、GoogleSignIn と FirebaseAuth を渡してあげる必要があります。(理由は後述)
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を使うためには GoogleSignIn と FirebaseAuth を渡してあげる必要があると書きました。
これするのが肝で、これをやらないとかなりUnit Testを書くのがめんどくさくなります。
※ Unit Test を書くときに Firebase まわりで色々とエラーになる。
※ これでかなり時間を溶かしました。
※ 例えば、 Firebase.initializeApp(); のMockを書くのがめんどいです。
GoogleSignIn と FirebaseAuth は 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() にしてしまい、怒られることがあるので注意。
コメント