dart-generate-test-mocks について
依存性注入、Mockito によるモック生成、スタブ設定、呼び出し検証を扱う、Dart 開発者向けのユニットテストガイドです。
主な機能
- 01依存性注入による外部サービスの分離
- 02Mockito でのモッククラス生成
- 03同期・非同期動作のスタブ設定
- 04モックとのやり取りの検証
- 05テストの実行と問題の診断
活用シーン
- 01API クライアントの分離テスト
- 02非同期メソッドの適切なモック化
- 03依存先とのやり取りの確認
- 04モック生成やテスト失敗の診断
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ flutter/ agent-plugins --skill dart-generate-test-mocksインストール後、Claude Code で/dart-generate-test-mocksを入力して読み込まれたか確認します。
詳細
Dart アプリケーションのテストとモック
目次
テストしやすいコードの構成
依存性注入に対応するように Dart クラスを設計します。API クライアントやデータベースなどの複雑な外部依存関係を切り離し、テスト時にモックオブジェクトへ置き換えられるようにします。
- 外部サービス(例:
http.Client)は、クラスのコンストラクターを通じて注入します。 - URL は必ず
Uriオブジェクトとして扱い、Uri.parse(string)を使用します。 - Dart のオブジェクト指向機能(クラス、ミックスイン)を活用して、外部とのやり取りを明確にするインターフェースを定義します。
依存関係の管理
必要なテスト用およびコード生成用パッケージを pubspec.yaml ファイルで設定します。
dart pub add httpを使って、実行時依存関係(例:package:http)を追加します。dart pub add dev:test dev:mockito dev:build_runnerを使って、テスト用依存関係を追加します。- 名前空間の衝突を避けるため、HTTP ライブラリはプレフィックスを付けてインポートします:
import 'package:http/http.dart' as http;。
モックの生成
package:mockito と build_runner を使って、固定シナリオのモッククラスを自動生成し、動作を検証します。
- 必ず
@GenerateNiceMocksアノテーションを使用します(スタブ不足による例外を避けるため、@GenerateMocksより推奨されます)。 - テストファイルにアノテーションを配置し、
MockSpec<Type>()オブジェクトのリストを渡します。 - 生成されたファイルは
.mocks.dart拡張子を使ってインポートします。 build_runnerを実行してモックファイルを生成します:dart run build_runner build。
単体テストの実装
生成されたモックオブジェクトを使って、テスト対象のシステムを分離します。package:test を使ってテストスイートを構成します。
- スタブの設定: テスト対象のシステムとやり取りする前に、モックの動作を設定します。
- 同期メソッドには
when(mock.method()).thenReturn(value)を使用します。 - 重要:
FutureまたはStreamを返すメソッドには、必ずthenAnswer((_) async => value)を使用します。非同期の戻り値にはthenReturnを使用しないでください。
- 同期メソッドには
- 検証: テスト対象のシステムがモックオブジェクトと正しくやり取りしたことを確認します。
- 正確な呼び出し回数を確認するには
verify(mock.method()).called(1)を使用します。 - 柔軟に検証するには、
any、anyNamed、captureAnyなどの引数マッチャーを使用します。
- 正確な呼び出し回数を確認するには
ワークフロー:モックを使ったテストの作成と実行
以下のチェックリストに沿って、モックを使った単体テストを実装し、検証します。
タスクの進捗
- 1. モック化する外部依存関係を特定します(例:
http.Client)。 - 2. 依存関係を対象クラスのコンストラクターに注入します。
- 3. テストファイル(例:
target_test.dart)を作成し、@GenerateNiceMocks([MockSpec<Dependency>()])を追加します。 - 4. 生成された
.mocks.dartファイルに対するpartまたはimportディレクティブを追加します。 - 5.
dart run build_runner buildを実行して、モッククラスを生成します。 - 6.
group()とtest()を使ってテストケースを記述します。 - 7.
when()を使って必要な動作をスタブ化します。 - 8. 対象のメソッドを実行します。
- 9.
verify()を使ってやり取りを検証し、expect()を使って結果を確認します。 - 10.
dart testを使ってテストスイートを実行します。
フィードバックループ:テストの失敗
テストが失敗した場合、または build_runner でエラーが発生した場合は、次の手順に従います。
- バリデーターを実行:
dart testまたはdart run build_runner buildを実行します。 - エラーを確認: スタブの不足、引数マッチャーの不一致、生成されたファイル内の構文エラーがないか確認します。
- 修正:
- モックメソッドが予期しない null エラーをスローする場合は、
@GenerateNiceMocksを使用していることを確認します。 - 非同期スタブが
ArgumentErrorをスローする場合は、thenReturnをthenAnswerに変更します。 build_runnerが失敗する場合は、.mocks.dartのインポートがファイル名と完全に一致していることを確認します。
- モックメソッドが予期しない null エラーをスローする場合は、
- すべてのテストが成功するまで繰り返します。
例
高忠実度のモックとテストの例
1. テスト対象システム(lib/api_service.dart)
import 'dart:convert';
import 'package:http/http.dart' as http;
class ApiService {
final http.Client client;
ApiService(this.client);
Future<String> fetchData(String urlString) async {
final uri = Uri.parse(urlString);
final response = await client.get(uri);
if (response.statusCode == 200) {
return jsonDecode(response.body)['data'];
} else {
throw Exception('Failed to load data');
}
}
}
2. テスト実装(test/api_service_test.dart)
import 'package:test/test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;
import 'package:my_app/api_service.dart';
// Generate the mock class for http.Client
@GenerateNiceMocks([MockSpec<http.Client>()])
import 'api_service_test.mocks.dart';
void main() {
group('ApiService', () {
late ApiService apiService;
late MockClient mockHttpClient;
setUp(() {
mockHttpClient = MockClient();
apiService = ApiService(mockHttpClient);
});
test('returns data if the http call completes successfully', () async {
// Arrange: Stub the async HTTP GET request using thenAnswer
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response('{"data": "Success"}', 200),
);
// Act
final result = await apiService.fetchData('https://api.example.com/data');
// Assert
expect(result, 'Success');
// Verify the mock was called with the correct Uri
verify(mockHttpClient.get(Uri.parse('https://api.example.com/data'))).called(1);
});
test('throws an exception if the http call completes with an error', () {
// Arrange
when(mockHttpClient.get(any)).thenAnswer(
(_) async => http.Response('Not Found', 404),
);
// Act & Assert
expect(
apiService.fetchData('https://api.example.com/data'),
throwsException,
);
});
});
}
コメント
Markdown 対応