1

How can I mock a function in flutter and verify it has been called n times?

Ive tried implementing Mock from mockito but it only throws errors:

class MockFunction extends Mock {
  call() {}
}

test("onListen is called once when first listener is registered", () {
      final onListen = MockFunction();

      // Throws: Bad state: No method stub was called from within `when()`. Was a real method called, or perhaps an extension method?
      when(onListen()).thenReturn(null);

      bloc = EntityListBloc(onListen: onListen);

      // If line with when call is removed this throws:
      // Used on a non-mockito object
      verify(onListen()).called(1);
    });

  });

As a workaround I am just manually tracking the calls:


test("...", () {
   int calls = 0;
   bloc = EntityListBloc(onListen: () => calls++);

   // ...

   expect(calls, equals(1));
});

So is there a way I can create simple mock functions for flutter tests?

Code Spirit
  • 3,992
  • 4
  • 23
  • 34

2 Answers2

3

What you could do is this:

class Functions  {
  void onListen() {}
}

class MockFunctions extends Mock implements Functions {}

void main() {
  test("onListen is called once when first listener is registered", () {
    final functions = MockFunctions();

    when(functions.onListen()).thenReturn(null);

    final bloc = EntityListBloc(onListen: functions.onListen);

    verify(functions.onListen()).called(1);
  });
}

Valentin Vignal
  • 6,151
  • 2
  • 33
  • 73
  • 1
    How about top level functions/methods? How do we test it? – Zolicious Jul 23 '22 at 14:06
  • You mean static methods? Unfortunately, static methods cannot be tested in dart. Look at the first comment of this question https://stackoverflow.com/questions/58238435/how-to-mock-a-static-method-in-flutter-with-mockito – Valentin Vignal Jul 23 '22 at 14:56
  • If you use dependency injection @Zolicious, you can add top-level functions as dependencies to your container. Check out the extra answer. – Christian Findlay Nov 21 '22 at 23:55
0

The accepted answer is correct, but it doesn't represent a real-life scenario where you will probably want to substitute a top-level function with a mock or a fake. This article explains how to include top-level functions in your dependency injection composition so that you can substitute those functions with mocks.

You can compose dependency injection like this and point to top-level functions such as launchUrl with ioc_container.

IocContainerBuilder compose() => IocContainerBuilder(
      allowOverrides: true,
    )
      ..addSingletonService<LaunchUrl>(
        (url, {mode, webOnlyWindowName, webViewConfiguration}) async =>
            launchUrl(
          url,
          mode: mode ?? LaunchMode.platformDefault,
          webViewConfiguration:
              webViewConfiguration ?? const WebViewConfiguration(),
          webOnlyWindowName: webOnlyWindowName,
        ),
      )
      ..add((container) => MyApp(launchUrl: container<LaunchUrl>()));

Then, you can use the technique mentioned in the answer here to mock with Mocktail.

import 'package:fafsdfsdf/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';

class LaunchMock extends Mock {
  Future<bool> call(
    Uri url, {
    LaunchMode? mode,
    WebViewConfiguration? webViewConfiguration,
    String? webOnlyWindowName,
  });
}

void main() {
  testWidgets('Test Url Launch', (tester) async {
    //These allow default values
    registerFallbackValue(LaunchMode.platformDefault);
    registerFallbackValue(const WebViewConfiguration());

    //Create the mock
    final mock = LaunchMock();
    when(() => mock(
          flutterDevUri,
          mode: any(named: 'mode'),
          webViewConfiguration: any(named: 'webViewConfiguration'),
          webOnlyWindowName: any(named: 'webOnlyWindowName'),
        )).thenAnswer((_) async => true);

    final builder = compose()
      //Replace the launch function with a mock
      ..addSingletonService<LaunchUrl>(mock);

    await tester.pumpWidget(
      builder.toContainer()<MyApp>(),
    );

    //Tap the icon
    await tester.tap(
      find.byIcon(Icons.favorite),
    );

    await tester.pumpAndSettle();

    verify(() => mock(flutterDevUri)).called(1);
  });
}
Christian Findlay
  • 6,770
  • 5
  • 51
  • 103