【问题标题】:How to unit test whether the ChangeNotifier's notifyListeners was called in Flutter/Dart?如何对 Flutter/Dart 中是否调用 ChangeNotifier 的 notifyListeners 进行单元测试?
【发布时间】:2020-05-12 21:12:35
【问题描述】:

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

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

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

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

这是我正在测试的课程(已简化,因此我可以在 StackOverflow 上分享):

import 'package:flutter/foundation.dart';

class ExampleModel extends ChangeNotifier {
  int _value = 0;

  int get value => _value;

  void increment() {
    _value++;
    notifyListeners();
  }
}

这就是我测试它的方式:

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);
    });
  });
}

另外,如果您认为以不同方式进行测试会更好,请告诉我,我目前在团队中作为 Flutter 开发人员单独工作,因此很难发现我是否走错了路。

【问题讨论】:

  • 没关系。我不太确定还有什么要添加的
  • 谢谢@RémiRousselet,我有点不确定,因为它需要一些手动代码,我想知道是否有更好的 mockito 方法
  • 你可以在这里使用 Mockito。但是由于监听器既没有参数也不需要返回一些东西,所以它不是很有用。

标签: unit-testing flutter dart provider


【解决方案1】:

您的方法对我来说似乎很好,但如果您想要一种更具描述性的方式,您还可以使用 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
    });
  });
}

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

【讨论】:

  • 不错的解决方案,但是,它对我不起作用:Error: Non-nullable variable 'exampleModel' must be assigned before it can be used. 知道为什么编译器没有在setUp 函数中获得赋值吗?
  • 这是由于 Dart 的 Sound-Null-Safety 特性。变量不允许有空值,需要立即分配变量。如果我们确定我们的变量将在以后分配,在这种情况下我们确定,那么您可以使用keyword late。所以在声明中late ExampleModel exampleModel 应该可以解决问题。
【解决方案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;
  }
}

然后是函数:

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;
}

参数:

  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);
});

【讨论】:

  • 问题是当expect()在监听回调中失败时,它只是抛出一个未处理的异常,但测试仍然会通过。
【解决方案3】:

我已经将它包装到函数中

import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';

dynamic checkNotifierCalled(
  ChangeNotifier notifier,
  Function() action, [
  Matcher? matcher,
]) {
  var isFired = false;
  void setter() {
    isFired = true;
    notifier.removeListener(setter);
  }

  notifier.addListener(setter);

  final result = action();
  // if asynchronous
  if (result is Future) {
    return result.then((value) {
      if (matcher != null) {
        expect(value, matcher);
      }
      return isFired;
    });
  } else {
    if (matcher != null) {
      expect(result, matcher);
    }
    return isFired;
  }
}

并通过以下方式调用它:

final isCalled = checkNotifierCalled(counter, () => counter.increment(), equals(2));
expect(isCalled, isTrue);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-30
    • 1970-01-01
    • 2019-09-18
    • 1970-01-01
    • 2012-10-29
    • 2018-12-06
    • 2020-01-18
    相关资源
    最近更新 更多