mcpField
D
dart-generate-test-mocks

dart-generate-test-mocks

github.com/flutter测试与质量3.1k14.7k 次安装2 天前更新
代码仓库

关于 dart-generate-test-mocks

为 Dart 开发者提供依赖注入、Mockito 模拟对象生成及单元测试的指导,涵盖异步桩设定与交互验证流程。

主要功能

  1. 01依赖注入以隔离外部服务
  2. 02使用 Mockito 生成模拟类
  3. 03配置同步与异步桩行为
  4. 04验证模拟对象交互
  5. 05运行并排查测试错误

适用场景

  1. 01为 API 客户端编写隔离测试
  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)。
  • 使用 Uri.parse(string) 严格将 URL 表示为 Uri 对象。
  • 利用 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,
      );
    });
  });
}

评论

支持 Markdown