mcpField
D
dart-generate-test-mocks

dart-generate-test-mocks

github.com/flutterテスト・品質3.1k14.7k 回インストール2 日前に更新
リポジトリ

dart-generate-test-mocks について

依存性注入、Mockito によるモック生成、スタブ設定、呼び出し検証を扱う、Dart 開発者向けのユニットテストガイドです。

主な機能

  1. 01依存性注入による外部サービスの分離
  2. 02Mockito でのモッククラス生成
  3. 03同期・非同期動作のスタブ設定
  4. 04モックとのやり取りの検証
  5. 05テストの実行と問題の診断

活用シーン

  1. 01API クライアントの分離テスト
  2. 02非同期メソッドの適切なモック化
  3. 03依存先とのやり取りの確認
  4. 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 でエラーが発生した場合は、次の手順に従います。

  1. バリデーターを実行: dart test または dart run build_runner build を実行します。
  2. エラーを確認: スタブの不足、引数マッチャーの不一致、生成されたファイル内の構文エラーがないか確認します。
  3. 修正:
    • モックメソッドが予期しない null エラーをスローする場合は、@GenerateNiceMocks を使用していることを確認します。
    • 非同期スタブが ArgumentError をスローする場合は、thenReturn を thenAnswer に変更します。
    • build_runner が失敗する場合は、.mocks.dart のインポートがファイル名と完全に一致していることを確認します。
  4. すべてのテストが成功するまで繰り返します。

例

高忠実度のモックとテストの例

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,
      );
    });
  });
}

コメント