Flutter模拟数据插件mockor的使用

Flutter模拟数据插件mockor的使用

mockor

Generic mocker method generator for mockitomocktail。 一个 mock 方法可以模拟任何类,就像在原始的 mockito 中一样。

开始使用

添加依赖

pubspec.yaml 文件中添加以下内容:

dev_dependencies:
  mockor: ^1.0.0

创建 mocker.dart 文件并定义模拟方法

在测试文件夹中创建一个名为 mocker.dart 的文件,并添加一个带有 @GenerateMocker 注解的模拟方法。别忘了导入 mockito

// 这个导入用于生成的 mockor 文件以添加 Mockito 的 `@GenerateMocks` 注解。
// 需要手动添加。
import 'package:mockito/mockor.dart';

// <file_name>.mocks.dart 将由 Mockito 生成,其中包含所有生成的模拟。
// 需要手动添加。
import 'example.mocks.dart';

import 'package:mockor/mockor.dart';

part 'example.mockor.dart';

abstract class ExampleUseCase {
  int exampleInt(int i);
}

abstract class ExampleUseCase2 {
  int? exampleNullableInt();
  void exampleVoid();
}

@GenerateMocker([
  ExampleUseCase,
  ExampleUseCase2,
])
T mock<T extends Object>({bool relaxed = false}) => _$mock<T>(relaxed: relaxed);

使用生成的模拟方法

在测试文件中导入并调用定义的模拟函数。

import '../../mocker.dart';

void main() {
  late ExampleUseCase exampleUseCase;
  late ExampleUseCase2 exampleUseCase2;

  setUp(() {
    // 这将返回 [MockExampleUseCase]
    exampleUseCase = mock();
    exampleUseCase2 = mock();
  });

  test("given exampleNullableInt throws an exception then don't catch it", () {
    when(exampleUseCase2.exampleNullableInt()).thenThrow(Exception());
    try {
      exampleUseCase2.exampleNullableInt();
      fail('expected exception');
    } on Exception {}
  });

  test('given exampleInt with any param returns 2 then return 2', () {
    /**
     * 默认会为所有 [GenerateMocker.types] 生成一个 `asMock` 扩展方法,
     * 它将其转换为生成的模拟类型(MockExampleUseCase)。
     * 由于空安全,我们只能在使用模拟类型时对非空参数使用 `[any]` 匹配器。
     * 请参阅 Mockito 的 [Null Safety README](https://github.com/dart-lang/mockito/blob/master/NULL_SAFETY_README.md) 获取更多信息。
     */
    when(exampleUseCase.asMock().exampleInt(any)).thenReturn(2);
    expect(exampleUseCase.exampleInt(1), 2);
  });

  test("given relaxed is true then return null on non null method not stubbed", () {
    final ExampleUseCase2 useCaseRelaxed = mock(relaxed: true);
    try {
      useCaseRelaxed.exampleNullableInt();
    } on MissingStubError {
      fail("did not expect $MissingStubError");
    }
  });

  // 注意:此测试仅在 `mockito` 中成功,而在 `mocktail` 中失败。
  test("given relaxed is false then don't throw exception on void method not stubbed", () {
    final ExampleUseCase2 useCase = mock(relaxed: false);
    try {
      useCase.exampleVoid();
    } on MissingStubError {
      fail("did not $MissingStubError");
    }
  });
}

使用 mocktail

修改 mocker.dart 文件

按照前面的步骤进行操作,但需要对 mocker.dart 文件进行一些修改。

// 这个导入用于生成模拟。
// 需要手动添加。
import 'package:mocktail/mocktail.dart';

import 'package:mockor/mockor.dart';

part 'mocker.mockor.dart';

@GenerateMocker.mocktail(
  [
    ExampleUseCase,
    ExampleUseCase2,
  ],
  // 可选参数:生成 `relaxedVoid` 参数以防止 `void` 和 `Future<void>` 缺失 stub 错误
  generateRelaxedVoidParameter: true,
  // 可选参数:生成非空参数的回退值以使用 `any()`
  generateMocktailFallbackValues: GenerateMocktailFallbackValues(
    [ExampleModel],
    // 可选参数:自动检测所有方法的所有非基本参数的模拟类
    autoDetect: true,
  ),
)
T mock<T extends Object>({bool relaxed = false, bool? relaxedVoid}) =>
    _$_mock<T>(relaxed: relaxed, relaxedVoid: relaxedVoid);

// 定义一个全局注册回退值的方法
void registerFallbackValuesAll() {
  _$registerFallbackValues();
}

创建 flutter_test_config.dart 文件

在测试文件夹的根目录下创建一个名为 flutter_test_config.dart 的文件。

import 'dart:async';
import 'package:flutter_test/flutter_test.dart';

import 'mocker.dart';

Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  // 这将在测试文件夹中的任何测试文件的主函数之前执行。
  setUpAll(() {
    registerFallbackValuesAll();
  });
  await testMain();
}

使用 relaxed 参数返回 null

_$mock<T>() 方法接受一个名为 relaxed 的可选布尔参数。

如果 false,则会在 Mock 对象上调用 throwOnMissingStub,这样当你尝试调用未 stub 的方法时会抛出异常。

但如果 true,对于非空返回类型,会抛出 TypeError。对于空返回类型,未 stub 的方法将返回 null。

默认情况下,mocktailtrue,而 mockitofalse

T mock<T extends Object>({bool relaxed = false}) => _$mock<T>(relaxed: relaxed);

final myService = mock<MyService>(relaxed: true);
when(myService.doSomethingElse()).thenReturn(true);

myService.doSomething(); // 这将抛出异常
myService.doSomethingElse(); // 这不会抛出异常

与原生 mockito 相比的优势

始终可以使用基类型

  • 重命名类不会破坏测试。
  • 查找用法不会遗漏任何内容。
  • 不必直接导入/依赖于模拟对象。

运行时的 relaxed 参数

生成的 _$mock 方法中添加了一个可选的 relaxed 参数。 当设置为 true 时,未 stub 的空方法返回 null。对于未 stub 的非空方法,则抛出 TypeError

这在上面已有详细解释。

与原生 mocktail 相比的优势

快速代码生成

尽管 mocktail 是为了避免代码生成而创建的,但在这种情况下仍需要代码生成,但仅限于包含通用模拟函数的一个文件。 你的任何测试都不需要依赖生成的代码。每个模拟类只有 1 行代码。

私有模拟类

生成的模拟类是私有的,因此不能被导入。只需调用全局 mock 方法。 这使得所有开发人员更容易遵循一致的编码约定。

生成 registerFallbackValues 函数

mocktail 的一个令人烦恼的缺点是你需要注册回退值才能调用它们上的 any() 函数。 使用 mockor,你可以生成一个 registerFallbackValues 函数,你可以在其中手动指定类型或让生成器自动检测它们。 有关更多信息,请参阅上面的开始部分。

可选的 relaxed 参数

生成的 _$mock 方法中添加了一个可选的 relaxed 参数。 当设置为 true 时,未 stub 的空方法返回 null。对于未 stub 的非空方法,则抛出 TypeError

这在上面已有详细解释。

可选的 relaxedVoid 参数(仅限 mocktail 特性)

生成的 _$mock 方法中添加了一个可选的 relaxedVoid 参数。 当设置为 true 时,未 stub 的 voidFuture<void> 方法不会抛出错误。

常见问题解答

如何隐藏生成的模拟类?

在项目的根目录下添加一个 analysis_options.yaml 文件,内容如下:

analyzer:
  exclude:
    - '**/*.mocks.dart' # Mockito @GenerateMocks

故障排除

错误:“未生成模拟类 ‘MockExampleUseCase’”

模拟方法仅支持返回其基类型的模拟实例。

错误:使用 any 时,“参数类型 ‘Null’ 无法分配给参数类型…”

为了能够使用 any,你需要的是被模拟的类型而不是基类型。 为此生成了一个 asMock 扩展方法。

extension ExampleUseCaseAsMockExtension on ExampleUseCase {
  MockExampleUseCase asMock() => this as MockExampleUseCase;
}

然后在测试中使用:

abstract class ExampleUseCase {
  int example(int i);
}
final ExampleUseCase useCase = mock();
// 因为 int i 是非空的,`any` 返回 null。所以必须使用 `asMock`,因为在 MockExampleUseCase 中该方法被重写为允许空参数。
when(useCase.asMock().example(any)).thenReturn(1);

更多关于Flutter模拟数据插件mockor的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter模拟数据插件mockor的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


mockor 是一个用于 Flutter 的模拟数据生成插件,它可以帮助开发者在开发和测试阶段生成模拟数据,从而避免依赖于真实的后端服务。通过 mockor,你可以轻松地创建和注入模拟数据,以便在开发过程中进行测试和调试。

以下是使用 mockor 插件的基本步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 mockor 插件的依赖。

dependencies:
  flutter:
    sdk: flutter
  mockor: ^1.0.0  # 请根据需要选择最新版本

然后运行 flutter pub get 来获取依赖。

2. 创建模拟数据类

接下来,你可以创建一个模拟数据类,并使用 @GenerateMocks 注解来标记它。mockor 会根据这个注解生成相应的模拟数据代码。

import 'package:mockor/mockor.dart';

@GenerateMocks([User])
class User {
  final String name;
  final int age;

  User(this.name, this.age);
}

3. 生成模拟数据代码

运行 flutter pub run build_runner build 来生成模拟数据代码。这将会在你的项目目录下生成一个 .mockor 文件,其中包含了模拟数据类的实现。

4. 使用模拟数据

生成代码后,你可以使用生成的模拟数据类来创建模拟对象。

import 'package:your_project/mockor.dart';

void main() {
  final mockUser = MockUser();
  mockUser.name = 'John Doe';
  mockUser.age = 30;

  print('Mock User: ${mockUser.name}, ${mockUser.age}');
}

5. 注入模拟数据

在开发过程中,你可以将模拟数据注入到你的应用中进行测试。例如,在依赖注入框架中,你可以将模拟数据类替换为真实的服务类。

import 'package:your_project/mockor.dart';

class UserService {
  final User user;

  UserService(this.user);

  void printUserInfo() {
    print('User: ${user.name}, ${user.age}');
  }
}

void main() {
  final mockUser = MockUser();
  mockUser.name = 'Jane Doe';
  mockUser.age = 25;

  final userService = UserService(mockUser);
  userService.printUserInfo();
}

6. 测试

在编写测试时,你可以使用 mockor 生成的模拟数据类来替代真实的依赖,从而简化测试流程。

import 'package:flutter_test/flutter_test.dart';
import 'package:your_project/mockor.dart';

void main() {
  test('UserService should print user info', () {
    final mockUser = MockUser();
    mockUser.name = 'Test User';
    mockUser.age = 99;

    final userService = UserService(mockUser);
    userService.printUserInfo();

    expect(mockUser.name, 'Test User');
    expect(mockUser.age, 99);
  });
}
回到顶部