mcpField
D
dart-generate-test-mocks

dart-generate-test-mocks

github.com/dhruvanbhalaraテスト・品質3048 回インストール9 週間前に更新
リポジトリ

dart-generate-test-mocks について

dart-generate-test-mocksは、モックを使うDartテスト向けのスキルです。フレームワークの選択、モック生成、振る舞いの設定、呼び出しの検証を扱います。

主な機能

  1. 01mocktailとmockitoの比較
  2. 02依存性注入を用いたコード構成
  3. 03build_runnerによるmockitoクラス生成
  4. 04非同期メソッドの適切なスタブ設定
  5. 05モック呼び出しの検証

活用シーン

  1. 01Dartテスト用モックフレームワークの選択
  2. 02サービス契約のモック生成
  3. 03非同期サービスの動作テスト
  4. 04呼び出し回数と想定外の操作の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dhruvanbhalara/skills --skill dart-generate-test-mocks

インストール後、Claude Code で/dart-generate-test-mocksを入力して読み込まれたか確認します。

詳細

目次

モックフレームワークの選択

基準mocktail(推奨)mockito
コード生成不要build_runner が必要
セットアップの手間最小限(class MockService extends Mock implements Service {})アノテーション + ジェネレーターのビルドステップ
型安全性実行時の検証コンパイル時に生成されるモッククラス
複雑な API手動でのフォールバック値(registerFallbackValue)@GenerateNiceMocks による自動処理

テスト可能なコードの構造化

効果的な単体テストを書くには、依存性注入を使ってコードベースを構造化します。物理的なレイヤー(ディスクストレージ、外部サーバー、プラットフォームチャネルなど)とやり取りする操作を分離し、テスト時にモックオブジェクトへ差し替えられるようにします。

  • コンストラクターインジェクション: すべてのサービスの依存関係(例: HTTP クライアント、データベースヘルパークライアント)は、クラス内で直接インスタンス化するのではなく、クラスのコンストラクターに渡します。
  • インターフェース分離: サービスの契約を表す明確な抽象基底クラスを定義します。モックは、具体的なサービス実装ではなく、理想的にはこれらの抽象的な契約を対象にすべきです。

開発用依存関係の管理

pubspec.yaml を設定して、コード生成とモックテストに必要なパッケージを指定します。

  1. dev_dependencies の下に対象のテストフレームワークとジェネレーターを追加します:
    dart pub add dev:test dev:mockito dev:build_runner
    
  2. テストファイル内で Mockito のアノテーションをインポートします:
    import 'package:mockito/annotations.dart';
    import 'package:mockito/mockito.dart';
    

モックファイルの生成

package:mockito と build_runner を併用して、モックの構造を自動生成します。

  • GenerateNiceMocks: 従来の @GenerateMocks ではなく、常に @GenerateNiceMocks アノテーションを使用します。nice モックは、事前にスタブを設定せずにメソッドが呼び出された場合に「MissingStubException」をスローする代わりに、null または一致するデフォルト値を自動的に返します。
  • MockSpec の設定: テストファイルのエントリポイントに @GenerateNiceMocks([MockSpec<YourService>()]) を付与します。
  • ミラー拡張のインポート: .mocks.dart サフィックスを使って、対応する生成ファイルをインポートします(例: import 'service_test.mocks.dart')。
  • ジェネレーターの実行: コマンドラインからコード生成を実行します:
    dart run build_runner build --delete-conflicting-outputs
    

スタブと検証のベストプラクティス

次のガイドラインに従って、信頼性の高いモックのやり取りを記述します。

  • Future と Stream のスタブ: Future または Stream を返すメソッドをスタブする場合は、必ず .thenAnswer((_) async => value) を使用します。非同期の戻り値に .thenReturn() を使うと実行時のキャストエラーが発生するため、決して使用しないでください。
  • 呼び出しの追跡: verify() API を使って、特定のメソッドが呼び出されたことを検証します。正確な呼び出し回数をアサートするには .called(number) を呼び出します。
  • 未使用のアサーション: verifyNever() または verifyNoMoreInteractions(mock) を使って、テストのライフサイクル中に予期しない操作が発生しなかったことを保証します。

ワークフロー: モックの生成とテストの検証

モック設定を確立するには、次のチェックリストに従ってください:

  • コンストラクタの確認: 対象のサービスクラスがコンストラクタ経由で依存関係を受け入れることを確認します。
  • テストファイルの作成: test/feature_test.dart を作成し、Mockito とテスト用ライブラリをインポートします。
  • エントリポイントへのアノテーション付与: main() の上に @GenerateNiceMocks([MockSpec<TargetService>()]) を追加します。
  • モックのインポート宣言: feature_test.mocks.dart を対象とするインポート文を追加します。
  • コードの生成: ターミナルで dart run build_runner build を実行し、モック化されたファイルを生成します。
  • モックのインスタンス化: setUp コールバック内で、生成されたモッククラスをインスタンス化します(例: mockService = MockTargetService())。
  • スタブの適用: when() を使用して、テストケース内で振る舞いを設定します。
  • 実行とアサート: テスト対象のシステムを呼び出し、expect() で出力を検証します。
  • 呼び出しの検証: verify() を使用して、モックとのやり取りをアサートします。

例

完全なモック化テストスイート(Mockito)

この例では、リモート API クライアントをモックして、データベース同期サービスをテストする方法を示します。

import 'package:test/test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;

// Define an abstract contract for a user repository
abstract class UserRepository {
  Future<String> getUserName(int id);
}

// System Under Test consuming the repository
class UserService {
  final UserRepository repository;

  UserService(this.repository);

  Future<String> fetchDisplayName(int id) async {
    try {
      final name = await repository.getUserName(id);
      return 'User: $name';
    } catch (_) {
      return 'Unknown User';
    }
  }
}

// 1. Annotate to generate MockUserRepository
@GenerateNiceMocks([MockSpec<UserRepository>()])
import 'user_service_test.mocks.dart';

void main() {
  group('UserService', () {
    late MockUserRepository mockRepo;
    late UserService userService;

    setUp(() {
      mockRepo = MockUserRepository();
      userService = UserService(mockRepo);
    });

    test('returns formatted display name when repository succeeds', () async {
      // 2. Arrange: Stub the async getUserName method
      when(mockRepo.getUserName(42)).thenAnswer(
        (_) async => 'Alice',
      );

      // 3. Act: Run target method
      final displayName = await userService.fetchDisplayName(42);

      // 4. Assert: Validate outcomes
      expect(displayName, equals('User: Alice'));

      // 5. Verify: Assert the repository method was invoked with correct arguments
      verify(mockRepo.getUserName(42)).called(1);
    });

    test('returns fallback string when repository throws exception', () async {
      // Arrange: Stub to throw error
      when(mockRepo.getUserName(any)).thenThrow(
        Exception('Connection Timeout'),
      );

      // Act
      final displayName = await userService.fetchDisplayName(99);

      // Assert
      expect(displayName, equals('Unknown User'));
    });
  });
}

コメント