[Flutter] Widgetテストにて、FirebaseAuthのテストをする

この記事は、【 [可茂IT塾 Advent Calendar 2023](https://qiita.com/advent-calendar/2023/kamo-it) 】の14日目の記事です。

TDD（テスト駆動開発）を行う時に、FirebaseAuth の認証系のテストについて
色々と調べたので、基本的な部分を記事にしました。誰かのお役に立てれば、幸いです。

この記事では、Flutterのテストについては詳しく説明していないので、
知りたい方は下の記事などが参考になると思われます。

https://qiita.com/yujikawa/items/fe509b160df5ab9eb74e

この記事では、Widgetテストにおける FirebaseAuth のテストについて書いてあります。
実機やエミュレーターを使用しないWidgetテストでは、FirebaseAuth のテストは、モックを使用して行います。

https://pub.dev/packages/firebase_auth_mocks

モック用のパッケージが用意されており、こちらを使用します。

ちなみに、状態管理は riverpod です。

ドキュメントにもテストの項目が用意されているので、こちらも参考になります。

https://riverpod.dev/ja/docs/cookbooks/testing

## はじめに

今回テストするのは、signOut のメソッドだとします。下記がコード例です。

```dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'auth.g.dart';

@Riverpod(keepAlive: true)
FirebaseAuth firebaseAuth(FirebaseAuthRef ref) => FirebaseAuth.instance;

@riverpod
UserAuth userAuth(UserAuthRef ref) {
  final instance = ref.watch(firebaseAuthProvider);
  return UserAuth(auth: instance);
}

class UserAuth {
  UserAuth({
    required FirebaseAuth auth,
  }) : _auth = auth;

  final FirebaseAuth _auth;

  Future<void> signOut() async {
    await _auth.signOut();
  }
}
```

FirebaseAuth.instance を Provider に持たせておきます。

下記が、テストするWidgetです。

```dart
ElevatedButton(
      onPressed: () async {
        final userAuth = ref.watch(userAuthProvider);
        await userAuth.signOut();
      },
      child: const Text('Sign Out!'),
    ),
```

次にテストのコードですが、FirebaseAuth のテストは、firebase_auth_mocks の Github に
コード例が書かれており、そちらがとても参考になります。

https://github.com/atn832/firebase_auth_mocks/blob/master/test/firebase_auth_mocks_test.dart

テストのコードです。

```dart
import 'package:firebase_auth_mocks/firebase_auth_mocks.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:x_test/auth/auth.dart';
import 'package:x_test/main.dart';

void main() {
  late MockFirebaseAuth mockFirebaseAuth;

  setUp(
    () {
      final tUser = MockUser(
        isAnonymous: false,
        email: 'bob@thebuilder.com',
        displayName: 'Bob Builder',
      );
      mockFirebaseAuth = MockFirebaseAuth(signedIn: true, mockUser: tUser);
    },
  );

  testWidgets('Sign Out Test', (WidgetTester tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [
          firebaseAuthProvider.overrideWithValue(mockFirebaseAuth),
        ],
        child: const MaterialApp(
          home: MyApp(),
        ),
      ),
    );

    // ログインしていることの確認
    expect(mockFirebaseAuth.currentUser, isNotNull);

    final signOutButtonFinder =
        find.widgetWithText(ElevatedButton, 'Sign Out!');

    // 描画を確認
    expect(signOutButtonFinder, findsOneWidget);

    // Buttonをタップ
    await tester.tap(signOutButtonFinder);
    await tester.pumpAndSettle();

    // ログアウトしたことの確認
    expect(mockFirebaseAuth.currentUser, isNull);
  });
}

```

上記のテストコードのように、FirebaseAuth.instance をモックの MockFirebaseAuth に override することで、signOut のメソッドを、モックの signOut メソッドに切り替えることができます。

この override さえできてしまえば、あとはサンプルコードを参考にして、createUserWithEmailAndPassword や signInAnonymously など、ほかのFirebaseAuth のテストに応用することができると思われます。

## 終わりに

riverpod のドキュメントにも書いてありますが、
テスト時にモックやスタブを返すクラスに override することを前提にアプリを設計しておくことが、効率よくテストする上でとても重要だと感じました。

もし、間違っているところやよりよい方法をご存知の方がいれば、ご指摘いただけると助かります。