flutter-tester について
Flutter プロジェクト向けに、Repository、DAO、サービス、Provider、Widget のテスト方法を示すスキルです。Given-When-Then による構成、依存関係のモック、実行コマンド、よくあるミスを扱います。
主な機能
- 01アーキテクチャ層ごとのテスト分離
- 02Given-When-Then 形式の利用
- 03Riverpod と Widget のテストガイド
- 04モック、Fake、依存関係の上書き方法
- 05テストコマンドとチェックリスト
活用シーン
- 01Repository と DAO のテスト作成
- 02Riverpod の状態とライフサイクルの検証
- 03Widget の操作とナビゲーションのテスト
- 04非同期処理、エラー、境界状態の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ harishwarrior/ flutter-claude-skills --skill flutter-testerインストール後、Claude Code で/flutter-testerを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/layer_testing_patterns.mdriverpod_testing_guide.mdwidget_testing_guide.md詳細
Flutter テスター
要件
flutter_test依存関係を含む Flutter プロジェクト- Riverpod、Mockito、GetIt と連携します
@GenerateMocksアノテーションを追加した後、dart run build_runner buildを実行してモックを生成します- FVM と互換性があります(
flutter testの代わりにfvm flutter testを使用します)
概要
Given-When-Then 構成を使って、各アーキテクチャ層を個別にテストします。成功パスとエラーパスの両方を必ずテストしてください。プロバイダーはモックせず、依存関係をオーバーライドしてください。
参照ファイル
テスト対象に応じて、該当するファイルを読み込んでください。
| テスト対象 | 参照ファイル |
|---|---|
| リポジトリ、DAO、サービスのロジック | references/layer_testing_patterns.md |
| ウィジェットの UI、操作、ダイアログ、ナビゲーション | references/widget_testing_guide.md |
| Riverpod プロバイダーの状態、変更、ライフサイクル | references/riverpod_testing_guide.md |
基本原則
1. レイヤーの分離
各レイヤーを、それぞれのモック依存関係に対してテストします。
| レイヤー | テスト対象 | モック対象 |
|---|---|---|
| リポジトリ | データソース間のデータ調整 | DAO、API、ロガー |
| DAO | データベースの CRUD 操作 | インメモリ DB を実際に使用し、ロガーはモックします |
| プロバイダー | 状態管理と遷移 | サービス、リポジトリ |
| サービス | ビジネスロジックとワークフロー | リポジトリ、ネットワーククライアント |
| ウィジェット | UI の動作と操作 | プロバイダーの依存関係(オーバーライド経由) |
2. Given-When-Then 構成
test('Given valid data, When fetchUsers called, Then returns user list', () async {
// Arrange (Given)
when(mockDAO.fetchAll()).thenAnswer((_) async => expectedUsers);
// Act (When)
final result = await repository.fetchUsers();
// Assert (Then)
expect(result, equals(expectedUsers));
verify(mockDAO.fetchAll()).called(1);
});
3. テストの構成
group('UserRepository', () {
group('fetchUsers', () {
setUp(() { /* init mocks, register with GetIt */ });
tearDown(() => GetIt.I.reset()); // Always reset GetIt
test('Given success ... When ... Then ...', () { });
test('Given error ... When ... Then ...', () { });
});
});
標準的なテストのセットアップ
モックの生成
@GenerateMocks([IUserDAO, IUserAPI, ILogger])
void main() { ... }
@GenerateMocks を変更した後、dart run build_runner build を実行します。
GetIt への登録
setUp(() {
mockDAO = MockIUserDAO();
mockLogger = MockILogger();
GetIt.I
..registerSingleton<IUserDAO>(mockDAO)
..registerSingleton<ILogger>(mockLogger);
});
tearDown(() => GetIt.I.reset()); // Critical — always reset
フェイクとモック
- フェイク(
class FakeLogger extends ILogger)— 呼び出しを検証する必要がない場合に使う、何もしないスタブです - モック(
MockILogger)—when()、verify()、thenThrow()が必要な場合に使います
クイックリファレンス
| シナリオ | 主なパターン |
|---|---|
| リポジトリをテストする | DAO + API をモックし、リポジトリのコンストラクターに注入します |
| DAO をテストする | setUp で FakeDatabase または openInMemoryDatabase() を使い、tearDown でテーブルを削除します |
| Riverpod プロバイダーをテストする | createContainer(overrides: [serviceProvider.overrideWith(...)]) |
| ウィジェットをテストする | 画面サイズを設定し、find.byKey() を使って、pumpAndSettle() を呼び出します |
| ローディング状態をテストする | Completer を使い、pump() でローディング状態を検証し、完了させてから再度 pump() を呼び出します |
| プラットフォーム固有の UI をテストする | debugDefaultTargetPlatformOverride = TargetPlatform.iOS — テスト後にリセットします |
| GoRouter のナビゲーションをテストする | FakeGoRouter + MockGoRouterProvider |
テストの実行
flutter test --coverage # All tests with coverage
flutter test test/path/to/test.dart # Specific file
flutter test --plain-name "Given valid data" # Filter by name
genhtml coverage/lcov.info -o coverage/html # Generate HTML coverage report
# Prefix any command with `fvm` if using Flutter Version Manager
よくある間違い
| 間違い | 修正方法 |
|---|---|
| プロバイダーを直接モックする | 依存関係をオーバーライドします:provider.overrideWith(...) |
tearDown で GetIt.I.reset() を呼び出していない | テスト同士が干渉します — 必ずリセットしてください |
テストで await Future.delayed() を使う | 代わりに await tester.pumpAndSettle() または Completer を使います |
| テキスト文字列でウィジェットを検索する | find.byKey(const Key('name')) を使います — テキストが変わっても安定します |
| ウィジェットテストで画面サイズを設定していない | tester.view.physicalSize = const Size(1000, 1000) を追加します |
debugDefaultTargetPlatformOverride をリセットしていない | テストの最後に null を設定します |
ラムダなしで tearDown() を使う | tearDown() async { ... } ではなく tearDown(() async { ... }) と記述します |
テストチェックリスト
セットアップとモック:
- 依存関係をモック化している(プロバイダーではない)
- 使用している場合は
SharedPreferencesをモック化している -
tearDownでGetIt.I.reset()を呼び出している -
tearDownでストリームを閉じている -
tearDownでコントローラーを破棄している
ウィジェットテスト:
- ソースウィジェットにキーを追加し、
find.byKey()で使用している - 画面サイズを設定している(
physicalSizeとdevicePixelRatio) - プラットフォームのオーバーライドをリセットしている(
debugDefaultTargetPlatformOverride = null) - 該当する場合はナビゲーションを検証している
テストカバレッジ:
- 成功パスと失敗パスをカバーしている
- エッジケースをテストしている(null、空、最大値)
- 読み込み状態とエラー状態をテストしている
- 非同期処理を適切に扱っている(
Future.delayedを使用していない)
コード品質:
- Given-When-Then の命名を使用している
- 適切な箇所で
verify()またはverifyNever()を使用している - テストが分離され、決定論的である
コメント
Markdown 対応