How to unit test whether the ChangeNotifier's notifyListeners was called in Flutter/Dart? How to unit test whether the ChangeNotifier's notifyListeners was called in Flutter/Dart? dart dart

How to unit test whether the ChangeNotifier's notifyListeners was called in Flutter/Dart?


Your approach seems fine to me but if you want to have a more descriptive way you could also use Mockito to register a mock callback function and test whether and how often the notifier is firing and thus notifying your registered mock instead of incrementing a counter:

import 'package:mobile_app/example_model.dart';import 'package:test/test.dart';/// Mocks a callback function on which you can use verifyclass 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    });  });}

Just keep in mind that if you trigger the same mock callback function in multiple tests you have to reset your mock callback function in the setup to reset its counter.


I've ran into the same Issue. It's difficult to test wether notifyListeners was called or not especially for async functions. So I took your Idea with the listenerCallCount and put it to one function you can use.

At first you need a ChangeNotifier:

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

Then the function:

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

Arguments:

  1. The ChangeNotifier you want to test.

  2. The function which should fire notifyListeners (just the reference to the function).

  3. A function to the state you want to test after each notifyListeners.

  4. A List of the expected values of the state you want to test after each notifyListeners (the order is important and the length has to equal the notifyListeners calls).

And this is how to test the 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);});


I have wrap it to the function

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

and call it by:

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