dart-generate-test-mocks

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing and Mocking Dart Applications

Dart应用的测试与模拟

Contents

目录

Structuring Code for Testability

为可测试性构建代码结构

Design Dart classes to support dependency injection. Isolate complex external dependencies (like API clients or databases) so they can be replaced with mock objects during testing.
  • Inject external services (e.g.,
    http.Client
    ) through class constructors.
  • Represent URLs strictly as
    Uri
    objects using
    Uri.parse(string)
    .
  • Utilize Dart's object-oriented features (classes, mixins) to define clear interfaces for external interactions.
设计Dart类以支持依赖注入。隔离API客户端或数据库等复杂外部依赖,以便在测试期间用模拟对象替换它们。
  • 通过类构造函数注入外部服务(例如
    http.Client
    )。
  • 使用
    Uri.parse(string)
    将URL严格表示为
    Uri
    对象。
  • 利用Dart的面向对象特性(类、混入)定义外部交互的清晰接口。

Managing Dependencies

依赖管理

Configure the
pubspec.yaml
file with the necessary testing and code generation packages.
  • Add runtime dependencies (e.g.,
    package:http
    ) using
    dart pub add http
    .
  • Add testing dependencies using
    dart pub add dev:test dev:mockito dev:build_runner
    .
  • Import HTTP libraries with a prefix to avoid namespace collisions:
    import 'package:http/http.dart' as http;
    .
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;

Generating Mocks

生成模拟对象

Use
package:mockito
and
build_runner
to automatically generate mock classes for fixed scenarios and behavior verification.
  • Always use the
    @GenerateNiceMocks
    annotation (preferable to
    @GenerateMocks
    to avoid missing stub exceptions).
  • Place the annotation in the test file, passing a list of
    MockSpec<Type>()
    objects.
  • Import the generated file using the
    .mocks.dart
    extension.
  • Execute
    build_runner
    to generate the mock files:
    dart run build_runner build
    .
使用
package:mockito
build_runner
自动生成用于固定场景和行为验证的模拟类。
  • 始终使用
    @GenerateNiceMocks
    注解(相比
    @GenerateMocks
    更优,可避免缺失存根异常)。
  • 在测试文件中放置该注解,传入
    MockSpec<Type>()
    对象列表。
  • 使用
    .mocks.dart
    扩展名导入生成的文件。
  • 执行
    build_runner
    生成模拟文件:
    dart run build_runner build

Implementing Unit Tests

实现单元测试

Isolate the system under test using the generated mock objects. Use
package:test
to structure the test suite.
  • Stubbing: Configure mock behavior before interacting with the system under test.
    • Use
      when(mock.method()).thenReturn(value)
      for synchronous methods.
    • CRITICAL: Always use
      thenAnswer((_) async => value)
      for methods returning a
      Future
      or
      Stream
      . Never use
      thenReturn
      for asynchronous returns.
  • Verification: Assert that the system under test interacted with the mock object correctly.
    • Use
      verify(mock.method()).called(1)
      to check exact invocation counts.
    • Use argument matchers like
      any
      ,
      anyNamed
      , or
      captureAny
      for flexible verification.
使用生成的模拟对象隔离被测系统。使用
package:test
构建测试套件。
  • 存根配置: 在与被测系统交互前配置模拟行为。
    • 对同步方法使用
      when(mock.method()).thenReturn(value)
    • 重点注意: 对返回
      Future
      Stream
      的方法,务必使用
      thenAnswer((_) async => value)
      。绝不要对异步返回使用
      thenReturn
  • 验证: 断言被测系统与模拟对象的交互是否正确。
    • 使用
      verify(mock.method()).called(1)
      检查精确调用次数。
    • 使用
      any
      anyNamed
      captureAny
      等参数匹配器进行灵活验证。

Workflow: Creating and Running Mocked Tests

工作流程:创建并运行带模拟的测试

Use the following checklist to implement and verify mocked unit tests.
使用以下清单实现并验证带模拟的单元测试。

Task Progress

任务进度

  • 1. Identify the external dependency to mock (e.g.,
    http.Client
    ).
  • 2. Inject the dependency into the target class constructor.
  • 3. Create a test file (e.g.,
    target_test.dart
    ) and add
    @GenerateNiceMocks([MockSpec<Dependency>()])
    .
  • 4. Add the
    part
    or
    import
    directive for the generated
    .mocks.dart
    file.
  • 5. Run
    dart run build_runner build
    to generate the mock classes.
  • 6. Write the test cases using
    group()
    and
    test()
    .
  • 7. Stub required behaviors using
    when()
    .
  • 8. Execute the target method.
  • 9. Verify interactions using
    verify()
    and assert outcomes using
    expect()
    .
  • 10. Run the test suite using
    dart test
    .
  • 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
    运行测试套件。

Feedback Loop: Test Failures

反馈循环:测试失败处理

If tests fail or
build_runner
encounters errors:
  1. Run validator: Execute
    dart test
    or
    dart run build_runner build
    .
  2. Review errors: Check for missing stubs, mismatched argument matchers, or syntax errors in the generated files.
  3. Fix:
    • If a mock method throws an unexpected null error, ensure you used
      @GenerateNiceMocks
      .
    • If an async stub throws an
      ArgumentError
      , change
      thenReturn
      to
      thenAnswer
      .
    • If
      build_runner
      fails, ensure the
      .mocks.dart
      import matches the file name exactly.
  4. Repeat until all tests pass.
如果测试失败或
build_runner
遇到错误:
  1. 运行验证器: 执行
    dart test
    dart run build_runner build
  2. 查看错误: 检查是否存在缺失的存根、不匹配的参数匹配器或生成文件中的语法错误。
  3. 修复:
    • 如果模拟方法抛出意外的空值错误,请确保使用了
      @GenerateNiceMocks
    • 如果异步存根抛出
      ArgumentError
      ,将
      thenReturn
      改为
      thenAnswer
    • 如果
      build_runner
      失败,请确保
      .mocks.dart
      的导入与文件名完全匹配。
  4. 重复上述步骤直到所有测试通过。

Examples

示例

High-Fidelity Mocking and Testing Example

高保真模拟与测试示例

1. System Under Test (
lib/api_service.dart
)
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 Implementation (
test/api_service_test.dart
)
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
([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,
      );
    });
  });
}
1. 被测系统(
lib/api_service.dart
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
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';

// 为http.Client生成模拟类
([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 {
      // 准备:使用thenAnswer存根异步HTTP GET请求
      when(mockHttpClient.get(any)).thenAnswer(
        (_) async => http.Response('{"data": "Success"}', 200),
      );

      // 执行
      final result = await apiService.fetchData('https://api.example.com/data');

      // 断言
      expect(result, 'Success');
      
      // 验证模拟对象是否使用正确的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', () {
      // 准备
      when(mockHttpClient.get(any)).thenAnswer(
        (_) async => http.Response('Not Found', 404),
      );

      // 执行 & 断言
      expect(
        apiService.fetchData('https://api.example.com/data'),
        throwsException,
      );
    });
  });
}