mcpField
D
dart-generate-test-mocks

dart-generate-test-mocks

github.com/dhruvanbhalara测试与质量3048 次安装9 周前更新
代码仓库

关于 dart-generate-test-mocks

dart-generate-test-mocks 面向使用 mock 的 Dart 测试开发,介绍框架选择、模拟类生成、行为配置和交互验证,帮助组织可测试的代码。

主要功能

  1. 01比较 mocktail 与 mockito
  2. 02通过依赖注入组织代码
  3. 03使用 build_runner 生成 mockito 类
  4. 04正确配置异步方法行为
  5. 05验证 mock 交互

适用场景

  1. 01为 Dart 测试套件选择 mock 框架
  2. 02为服务契约生成 mock
  3. 03测试异步服务行为
  4. 04断言方法调用并检查意外交互

安装

复制下面的命令,在终端中运行。

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

安装后在 Claude Code 中输入/dart-generate-test-mocks确认已加载。

详细介绍

目录

Mock 框架选择

标准mocktail(推荐)mockito
代码生成无需生成需要 build_runner
设置开销极少(class MockService extends Mock implements Service {})注解 + 生成器构建步骤
类型安全运行时验证编译时生成 Mock 类
复杂 API手动提供回退值(registerFallbackValue)自动处理 @GenerateNiceMocks

面向可测试性组织代码

若要编写有效的单元测试,请使用依赖注入来组织代码库。隔离与物理层交互的操作(例如磁盘存储、外部服务和平台通道),以便在测试时用 Mock 对象替换它们:

  • 构造函数注入:将所有服务依赖项(例如 HTTP 客户端、数据库辅助客户端)传入类构造函数,而不是在类内部直接实例化。
  • 接口隔离:定义清晰的抽象基类,用于表示服务契约。理想情况下,Mock 应针对这些抽象契约,而不是具体的服务实现。

管理开发依赖项

在 pubspec.yaml 中配置代码生成和 Mock 测试所需的软件包:

  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';
    

生成 Mock 文件

结合使用 package:mockito 和 build_runner,自动生成 Mock 结构:

  • GenerateNiceMocks:始终使用 @GenerateNiceMocks 注解,而不是旧版的 @GenerateMocks。当调用方法时,如果未预先配置 Stub,Nice Mock 会自动返回 null 或匹配的默认值,而不是抛出 "MissingStubException"。
  • MockSpec 配置:使用 @GenerateNiceMocks([MockSpec<YourService>()]) 标注测试文件的入口点。
  • Mirror 扩展导入:使用 .mocks.dart 后缀导入对应的生成文件(例如 import 'service_test.mocks.dart')。
  • 运行生成器:通过 CLI 触发代码生成:
    dart run build_runner build --delete-conflicting-outputs
    

Stub 和验证的最佳实践

遵循以下指南,编写可靠的 Mock 交互:

  • Future 和 Stream 的 Stub 设置:为返回 Future 或 Stream 的方法设置 Stub 时,始终使用 .thenAnswer((_) async => value)。异步返回值绝不能使用 .thenReturn(),否则会导致运行时类型转换错误。
  • 调用跟踪:使用 verify() API 验证是否调用了指定方法。调用 .called(number) 断言精确的调用次数。
  • 未使用的调用断言:使用 verifyNever() 或 verifyNoMoreInteractions(mock),确保测试生命周期内没有发生意外操作。

工作流:生成 Mock 并验证测试

按照此清单设置 Mock 配置:

  • 检查构造函数:确保目标服务类通过构造函数接收依赖项。
  • 编写测试文件:创建 test/feature_test.dart,并导入 Mockito 和测试库。
  • 标注入口点:在 main() 上方添加 @GenerateNiceMocks([MockSpec<TargetService>()])。
  • 声明 Mock 导入:添加指向 feature_test.mocks.dart 的导入语句。
  • 生成代码:在终端中运行 dart run build_runner build,以创建 Mock 文件。
  • 实例化 Mock:在 setUp 回调中实例化生成的 Mock 类(例如 mockService = MockTargetService())。
  • 设置桩行为:在测试用例中使用 when() 配置行为。
  • 执行并断言:调用被测系统,并通过 expect() 验证输出。
  • 验证调用:使用 verify() 断言 Mock 的交互。

示例

完整的 Mockito Mock 测试套件

此示例展示了如何 Mock 远程 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'));
    });
  });
}

评论

支持 Markdown