如何在 Flutter/Dart 中单元测试 ChangeNotifier 的 notificationListener 是否被调用?

Vin*_*rga 11 provider unit-testing dart flutter

provider在我们的应用程序中使用该包,我想ChangeNotifier单独测试我的类,以进行简单的单元测试来检查业务逻辑。

除了属性值之外ChangeNotifier,我还想确保在某些情况下(必要时),已notifyListeners调用 ,否则,依赖于此类的最新信息的小部件将不会被更新。

目前,我正在间接测试 是否已notifyListeners被调用:我正在使用这样一个事实:ChangeNotifier允许我使用其方法添加回调addListener。在我添加到测试套件中的回调中,我只需增加一个整数计数器变量并对其进行断言。

这是测试我是否ChangeNotifier调用其侦听器的正确方法吗?有没有更具描述性的方法来测试这个?

这是我正在测试的课程:

import 'package:flutter/foundation.dart';

class ExampleModel extends ChangeNotifier {
  int _value = 0;

  int get value => _value;

  void increment() {
    _value++;
    notifyListeners();
  }
}
Run Code Online (Sandbox Code Playgroud)

这就是我测试它的方法:

import 'package:mobile_app/example_model.dart';
import 'package:test/test.dart';

void main() {
  group('$ExampleModel', () {
    ExampleModel exampleModel;
    int listenerCallCount;

    setUp(() {
      listenerCallCount = 0;
      exampleModel = ExampleModel()
        ..addListener(() {
          listenerCallCount += 1;
        });
    });

    test('increments value and calls listeners', () {
      exampleModel.increment();
      expect(exampleModel.value, 1);
      exampleModel.increment();
      expect(listenerCallCount, 2);
    });

    test('unit tests are independent from each other', () {
      exampleModel.increment();
      expect(exampleModel.value, 1);
      exampleModel.increment();
      expect(listenerCallCount, 2);
    });
  });
}
Run Code Online (Sandbox Code Playgroud)

Bao*_*Ngo 11

你的方法对我来说似乎很好,但如果你想要一种更具描述性的方式,你也可以使用Mockito注册一个模拟回调函数并测试通知程序是否触发以及触发的频率,从而通知你注册的模拟而不是增加计数器:

import 'package:mobile_app/example_model.dart';
import 'package:test/test.dart';

/// Mocks a callback function on which you can use verify
class MockCallbackFunction extends Mock {
  call();
}
void main() {
  group('$ExampleModel', () {
    late ExampleModel exampleModel;
    final notifyListenerCallback = MockCallbackFunction(); // Your callback function mock

    setUp(() {
      exampleModel = ExampleModel()
        ..addListener(notifyListenerCallback);
      reset(notifyListenerCallback); // resets your mock before each test
    });

    test('increments value and calls listeners', () {
      exampleModel.increment();
      expect(exampleModel.value, 1);
      exampleModel.increment();
      verify(notifyListenerCallback()).called(2); // verify listener were notified twice
    });

    test('unit tests are independent from each other', () {
      exampleModel.increment();
      expect(exampleModel.value, 1);
      exampleModel.increment();
      expect(notifyListenerCallback()).called(2); // verify listener were notified twice. This only works, if you have reset your mocks
    });
  });
}
Run Code Online (Sandbox Code Playgroud)

请记住,如果您在多个测试中触发相同的模拟回调函数,则必须在设置中重置模拟回调函数以重置其计数器。


Tor*_*ler 2

我遇到了同样的问题。很难测试是否notifyListeners被调用,特别是对于async函数。因此,我将您的想法与listenerCallCount并将其放入您可以使用的一个功能中。

首先你需要一个ChangeNotifier

class Foo extends ChangeNotifier{
  int _i = 0;
  int get i => _i;
  Future<bool> increment2() async{
    _i++;
    notifyListeners();
    _i++;
    notifyListeners();
    return true;
  }
}
Run Code Online (Sandbox Code Playgroud)

然后函数:

Future<R> expectNotifyListenerCalls<T extends ChangeNotifier, R>(
    T notifier,
    Future<R> Function() testFunction,
    Function(T) testValue,
    List<dynamic> matcherList) async {
  int i = 0;
  notifier.addListener(() {
    expect(testValue(notifier), matcherList[i]);
    i++;
  });
  final R result = await testFunction();
  expect(i, matcherList.length);
  return result;
}
Run Code Online (Sandbox Code Playgroud)

论据:

  1. ChangeNotifier想要测试的。

  2. 应该触发的函数notifyListeners(只是函数的引用)。

  3. 您想要在每个 后测试的状态的函数notifyListeners

  4. 每个之后要测试的状态的预期值列表notifyListeners(顺序很重要,长度必须等于调用notifyListeners)。

这是测试的方法ChangeNotifier

test('should call notifyListeners', () async {
  Foo foo = Foo();

  expect(foo.i, 0);

  bool result = await expectNotifyListenerCalls(
      foo,
      foo.increment2,
      (Foo foo) => foo.i,
      <dynamic>[isA<int>(), 2]);

  expect(result, true);
});
Run Code Online (Sandbox Code Playgroud)