使用 Mockito 模拟依赖项
有时,单元测试可能依赖于从实时网络服务或数据库获取数据的类。这由于以下几个原因而不太方便:
- 调用实时服务或数据库会降低测试执行速度。
- 如果网络服务或数据库返回意外结果,则通过的测试可能会开始失败。这被称为“不稳定测试”。
- 通过使用实时网络服务或数据库很难测试所有可能的成功和失败场景。
因此,您可以“模拟”这些依赖项,而不是依赖于实时网络服务或数据库。模拟允许模拟实时网络服务或数据库,并根据情况返回特定结果。
一般来说,您可以通过创建类的替代实现来模拟依赖项。您可以手动编写这些替代实现,也可以使用Mockito 包作为快捷方式。
此方法演示了使用以下步骤使用 Mockito 包进行模拟的基础知识:
- 添加包依赖项。
- 创建要测试的函数。
- 使用模拟的 http.Client创建测试文件。
- 为每个条件编写测试。
- 运行测试。
有关更多信息,请参阅Mockito 包文档。
1. 添加包依赖项
#要使用 mockito 包,请将其与 dev_dependencies 部分中的 flutter_test 依赖项一起添加到 pubspec.yaml 文件中。
此示例还使用 http 包,因此请在 dependencies 部分中定义该依赖项。
mockito: 5.0.0 通过代码生成支持 Dart 的空安全。要运行所需的代码生成,请在 dev_dependencies 部分中添加 build_runner 依赖项。
要添加依赖项,请运行 flutter pub add:
flutter pub add http dev:mockito dev:build_runner2. 创建要测试的函数
#在此示例中,将对从互联网获取数据 方法中的 fetchAlbum 函数进行单元测试。要测试此函数,请进行以下两处更改:
- 为函数提供一个 http.Client。这允许根据情况提供正确的http.Client。对于 Flutter 和服务器端项目,请提供一个http.IOClient。对于浏览器应用程序,请提供一个http.BrowserClient。对于测试,请提供一个模拟的http.Client。
- 使用提供的 client从互联网获取数据,而不是难以模拟的静态http.get()方法。
该函数现在应如下所示:
Future<Album> fetchAlbum(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
  if (response.statusCode == 200) {
    // 如果服务器返回 200 OK 响应,
    // 则解析 JSON。
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // 如果服务器未返回 200 OK 响应,
    // 则抛出异常。
    throw Exception('Failed to load album');
  }
}在您的应用程序代码中,您可以使用 fetchAlbum(http.Client()) 直接为 fetchAlbum 方法提供一个 http.Client。http.Client() 创建一个默认的 http.Client。
3. 使用模拟的 http.Client 创建测试文件
#接下来,创建一个测试文件。
按照单元测试简介 方法中的建议,在根 test 文件夹中创建一个名为 fetch_album_test.dart 的文件。
将 @GenerateMocks([http.Client]) 注解添加到主函数以使用 mockito 生成 MockClient 类。
生成的 MockClient 类实现了 http.Client 类。这允许您将 MockClient 传递给 fetchAlbum 函数,并在每个测试中返回不同的 http 响应。
生成的模拟将位于 fetch_album_test.mocks.dart 中。导入此文件以使用它们。
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
// 使用 Mockito 包生成 MockClient。
// 在每个测试中创建此类的新实例。
@GenerateMocks([http.Client])
void main() {
}接下来,运行以下命令生成模拟:
dart run build_runner build4. 为每个条件编写测试
#fetchAlbum() 函数执行以下两项操作之一:
- 如果 http 调用成功,则返回 Album
- 如果 http 调用失败,则抛出 Exception
因此,您需要测试这两个条件。使用 MockClient 类为成功测试返回“确定”响应,为不成功测试返回错误响应。使用 Mockito 提供的 when() 函数测试这些条件:
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'fetch_album_test.mocks.dart';
// 使用 Mockito 包生成 MockClient。
// 在每个测试中创建此类的新实例。
@GenerateMocks([http.Client])
void main() {
  group('fetchAlbum', () {
    test('如果 http 调用成功完成,则返回 Album', () async {
      final client = MockClient();
      // 使用 Mockito 在调用提供的 http.Client 时返回成功的响应。
      when(client
              .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
          .thenAnswer((_) async =>
              http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200));
      expect(await fetchAlbum(client), isA<Album>());
    });
    test('如果 http 调用以错误完成,则抛出异常', () {
      final client = MockClient();
      // 使用 Mockito 在调用提供的 http.Client 时返回不成功的响应。
      when(client
              .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
          .thenAnswer((_) async => http.Response('Not Found', 404));
      expect(fetchAlbum(client), throwsException);
    });
  });
}5. 运行测试
#现在您已经准备好了带有测试的 fetchAlbum() 函数,请运行测试。
flutter test test/fetch_album_test.dart您还可以按照单元测试简介 方法中的说明,在您喜欢的编辑器中运行测试。
完整示例
#lib/main.dart
#import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
  if (response.statusCode == 200) {
    // 如果服务器返回 200 OK 响应,
    // 则解析 JSON。
    return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
  } else {
    // 如果服务器未返回 200 OK 响应,
    // 则抛出异常。
    throw Exception('Failed to load album');
  }
}
class Album {
  final int userId;
  final int id;
  final String title;
  const Album({required this.userId, required this.id, required this.title});
  factory Album.fromJson(Map<String, dynamic> json) {
    return Album(
      userId: json['userId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
    );
  }
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
  const MyApp({super.key});
  @override
  State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
  late final Future<Album> futureAlbum;
  @override
  void initState() {
    super.initState();
    futureAlbum = fetchAlbum(http.Client());
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Fetch Data Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Fetch Data Example'),
        ),
        body: Center(
          child: FutureBuilder<Album>(
            future: futureAlbum,
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return Text(snapshot.data!.title);
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }
              // 默认情况下,显示加载微调器。
              return const CircularProgressIndicator();
            },
          ),
        ),
      ),
    );
  }
}test/fetch_album_test.dart
#import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:mocking/main.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'fetch_album_test.mocks.dart';
// 使用 Mockito 包生成 MockClient。
// 在每个测试中创建此类的新实例。
@GenerateMocks([http.Client])
void main() {
  group('fetchAlbum', () {
    test('如果 http 调用成功完成,则返回 Album', () async {
      final client = MockClient();
      // 使用 Mockito 在调用提供的 http.Client 时返回成功的响应。
      when(client
              .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
          .thenAnswer((_) async =>
              http.Response('{"userId": 1, "id": 2, "title": "mock"}', 200));
      expect(await fetchAlbum(client), isA<Album>());
    });
    test('如果 http 调用以错误完成,则抛出异常', () {
      final client = MockClient();
      // 使用 Mockito 在调用提供的 http.Client 时返回不成功的响应。
      when(client
              .get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1')))
          .thenAnswer((_) async => http.Response('Not Found', 404));
      expect(fetchAlbum(client), throwsException);
    });
  });
}总结
#在此示例中,您学习了如何使用 Mockito 测试依赖于网络服务或数据库的函数或类。这只是对 Mockito 库和模拟概念的简要介绍。有关更多信息,请参阅Mockito 包提供的文档。
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。