お久しぶりです。みやかわです。
お仕事で古の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 を使うには、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() にしてしまい、怒られることがあるので注意。