Giter Site home page Giter Site logo

felangel / mocktail Goto Github PK

View Code? Open in Web Editor NEW
582.0 12.0 78.0 175 KB

A mock library for Dart inspired by mockito

Home Page: https://pub.dev/packages/mocktail

License: MIT License

Dart 99.48% Shell 0.52%
mocking mock mocks mockito dart dart-package testing mocktail mock-library flutter

mocktail's Introduction

🍹 mocktail

ci coverage License: MIT

This repository contains mocking libraries for Dart inspired by mockito.


Pub

A Dart mocking library which simplifies mocking with null safety support and no manual mocks or code generation.


Pub

A Dart package which allows you to mock Image.network in your widget tests with confidence using package:mocktail

mocktail's People

Contributors

abausg avatar alestiago avatar daohoangson avatar davidlj95 avatar denangelov avatar felangel avatar goddchen avatar jamesblasco avatar jorgecoca avatar kirpal avatar mideb avatar mousedownmike avatar provokateurin avatar renancaraujo avatar rocboronat avatar rrousselgit avatar woolfred avatar zeshuaro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mocktail's Issues

Unit tests with mocktail pass but 'Null' is not a subtype of type 'Future<>' runtime exception is thrown

Hi @felangel!

Hope all is well :-)

I posted this originally on SO here, but after a couple of days, no one seems to have a good answer.

There are similar issues asking about the same error (e.g. here), but their cause was due to improper mocking. In my case, I seem to have the method properly mocked, yet when I debug in Visual Studio Code with All Exceptions enabled, then I get the runtime exception:

_TypeError (type 'Null' is not a subtype of type 'Future<AuthenticationToken?>')

If I continue the test past the exception (or simply debug the tests with All Exceptions disabled or simply run them without debugging), all my tests pass ok.

flutter doctor -v
[βœ“] Flutter (Channel stable, 2.5.3, on macOS 11.6 20G165 darwin-x64, locale en-US)
    β€’ Flutter version 2.5.3 at /Users/davilin/Documents/Projects/flutter
    β€’ Upstream repository https://github.com/flutter/flutter.git
    β€’ Framework revision 18116933e7 (7 days ago), 2021-10-15 10:46:35 -0700
    β€’ Engine revision d3ea636dc5
    β€’ Dart version 2.14.4

[βœ“] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
    β€’ Android SDK at /Users/davilin/Library/Android/sdk
    β€’ Platform android-31, build-tools 31.0.0
    β€’ Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    β€’ Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165)
    β€’ All Android licenses accepted.

[βœ“] Xcode - develop for iOS and macOS
    β€’ Xcode at /Applications/Xcode.app/Contents/Developer
    β€’ Xcode 13.0, Build version 13A233
    β€’ CocoaPods version 1.11.2

[βœ“] Chrome - develop for the web
    β€’ Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[βœ“] Android Studio (version 2020.3)
    β€’ Android Studio at /Applications/Android Studio.app/Contents
    β€’ Flutter plugin can be installed from:
      πŸ”¨ https://plugins.jetbrains.com/plugin/9212-flutter
    β€’ Dart plugin can be installed from:
      πŸ”¨ https://plugins.jetbrains.com/plugin/6351-dart
    β€’ Java version OpenJDK Runtime Environment (build 11.0.10+0-b96-7281165)

[βœ“] VS Code (version 1.61.2)
    β€’ VS Code at /Applications/Visual Studio Code.app/Contents
    β€’ Flutter extension version 3.27.0

[βœ“] Connected device (2 available)
    β€’ Davi’s iPhone (mobile) β€’ 3ad3d60608b32fc9683368619da2e4da310ab7f2 β€’ ios            β€’ iOS 12.5.5 16H62
    β€’ Chrome (web)           β€’ chrome                                   β€’ web-javascript β€’ Google Chrome 94.0.4606.81
dependencies:
  hive: ^2.0.4
  hive_flutter: ^1.1.0

dev_dependencies:
  mocktail: ^0.1.4
import 'package:hive/hive.dart';

class AuthenticationRepository {
  static const _currentTokenKey = 'key';
  AuthenticationToken? _inMemoryToken;
  Future<Box<AuthenticationToken?>> _tokenBox;
  ...
  Future<AuthenticationToken?> activeToken() async =>
      _inMemoryToken ?? (await _tokenBox).get(_currentTokenKey);
  ...
}

Sample test file:

import 'package:app/src/data/authentication/repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class MockAuthenticationRepository extends Mock
    implements AuthenticationRepository {}

void main() {
  AuthenticationRepository authenticationRepository;
  SUT sut; // SUT depends on AuthenticationRepository

  setUp(() {
    authenticationRepository = MockAuthenticationRepository();

    when(() => authenticationRepository.activeToken())
      .thenAnswer((realInvocation) => Future.value(AuthenticationToken()));
    
    sut = SUT(authenticationRepository);
  });

  test('some test', () async {
    await sut.someMethod();
    verify(() => authenticationRepository.activeToken()).called(1);
  });
}

mocktail exception

Here is the stack trace:

MockAuthenticationRepository.activeToken (/Users/davilin/Documents/Projects/app/flutter/app/lib/src/data/authentication/repository.dart:296)
main.initMocks.<anonymous closure> (/Users/davilin/Documents/Projects/app/flutter/app/test/network/token_refresh_interceptor_test.dart:33)
when.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/mocktail-0.1.4/lib/src/mocktail.dart:211)
main.initMocks (/Users/davilin/Documents/Projects/app/flutter/app/test/network/token_refresh_interceptor_test.dart:33)
main.<anonymous closure>.<anonymous closure> (/Users/davilin/Documents/Projects/app/flutter/app/test/network/token_refresh_interceptor_test.dart:52)
Declarer._runSetUps.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.2/lib/src/backend/declarer.dart:329)
Future.forEach.<anonymous closure> (dart:async/future.dart:495)
Future.doWhile.<anonymous closure> (dart:async/future.dart:535)
StackZoneSpecification._registerUnaryCallback.<anonymous closure>.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/src/stack_zone_specification.dart:126)
StackZoneSpecification._run (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/src/stack_zone_specification.dart:208)
StackZoneSpecification._registerUnaryCallback.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/src/stack_zone_specification.dart:126)
_rootRunUnary (dart:async/zone.dart:1436)
_CustomZone.runUnary (dart:async/zone.dart:1335)
_CustomZone.runUnaryGuarded (dart:async/zone.dart:1244)
_CustomZone.bindUnaryCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1281)
Future.doWhile (dart:async/future.dart:551)
Future.forEach (dart:async/future.dart:493)
Declarer._runSetUps (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/test_api-0.4.2/lib/src/backend/declarer.dart:329)
<asynchronous gap> (Unknown Source:0)
StackZoneSpecification._registerUnaryCallback.<anonymous closure> (/Users/davilin/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.10.0/lib/src/stack_zone_specification.dart:0)
<asynchronous gap> (Unknown Source:0)

Some feedback on SO suggested it may be due to optional variable types I'm passing back from the function, but after trying the following to make what I'm returning explicitly non-optional, the same exception occurs:

Future<AuthenticationToken?> activeToken() => Future<AuthenticationToken?>(() => const AuthenticationToken()); 

Future<AuthenticationToken?> activeToken() async => const AuthenticationToken();

Future<AuthenticationToken?> activeToken() => _activeToken(); 
Future<AuthenticationToken?> _activeToken() async => _inMemoryToken ?? (await _tokenBox).get(_currentTokenKey);

Finally, making the return type an optional Future does avoid the exception, but implies something wrong about mocktail, such that it is expecting an optional Future as the return type, whereas the actual return type is explicitly non-optional. And, of course, this wouldn't work as a real solution, because it complicates the calling code.

Future<AuthenticationToken?>? activeToken() async =>
    _inMemoryToken ?? (await _tokenBox).get(_currentTokenKey);

Thanks for reading and if you can shed some light on this!

Default, common use stubs

Mocktail could provide some default stub instances that are common use and not project specific.

A common one that I usually end up writing on every project is a VoidCallbackStub, usually to check if a onPressed callback was called on widget testing.

An implementation suggestion:

class _VoidCallbackStub {
  void onCall() {}
}

class VoidCallbackStub extends Mock implements _VoidCallbackStub {}

then we could use it like the following:

      final listener = VoidCallbackStub();
      await _pumpWidget(
        tester,
        CustomButton(
          label: 'label',
          onPressed: listener.onCall,
        ),
      );

      await tester.tap(find.byType(GestureDetector));
      await tester.pumpAndSettle();
   
      verify(listener.onCall).called(1);

Is this something worth adding to Mocktail?

[Not A Bug]: How to stub inner callbacks / closure?

Hello!

i am trying to mock the following method of sqlite_api.dart by (https://pub.dev/packages/sqflite):

Future<T> transaction<T>(Future<T> Function(Transaction txn) action, {bool? exclusive});

my implementation/adapting of the method is like:

Future<void> _transaction(Set<DatabaseLocalRequest> payload) async {
    await this._api.transaction((txn) async => {
      for (final req in payload) {
        await txn.rawInsert(req.query.sql, req.query.arguments)
      }
    });
}

my db_test.dart using Mocktail (https://pub.dev/packages/mocktail):

class MockDb extends Mock implements Database {}
...
test('if [single] put succeeds', () async {
      // SETUP
      sut = DatabaseLocalProvider(db: mockDb);
      final query = Statement(sql: 'INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');
      final req = DatabaseLocalRequest(query: query);

      // MOCK
      when(() => mockDb.transaction((txn) => txn.rawInsert(req.query.sql, req.query.arguments)))
          .thenAnswer((_) async => 1);

      // ACT, ASSERT
      await sut.put(req: req, bulkReq: null).then((response) => {
        expect(response, ...
      });
}); // test end

I got the following response from the console ERROR:
🚨🚨

type 'Null' is not a subtype of type 'Future<Set<Set<int>>>'

How do I stub the inner txn.rawInsert() method that should respond with the Future<Set<Set<int>>> with {{1}}?

Thanks in advance!

Add ArgThat Function

Is your feature request related to a problem? Please describe.
I'm replacing Mockito with Mocktail and would like to use the same (or similar) way of testing that we already do.

Describe the solution you'd like
Please see Mockito's docs and the commit that added this function

Describe alternatives you've considered
I'm open to alternative ways of writing this, but thought I'd see if it was of interest here.

Additional context
We have some tests where we've created our own custom matchers to be used with Mockito's argThat function.

Most of our tests are checking type equality this way though:

// Mockito syntax
verify(graphQLDataSource.query(
        argThat(isA<CafesByKeywordQuery>()),
    )).called(1);

Not sure how to verify a void method was called

Describe the bug
Given 2 classes A and B, when A uses a void method of B, we can't verify that this call happens.

To Reproduce
Full example in one simple file: https://gist.github.com/svarlet/337c0d3234cc3ba67c7d96adf788c092
Run the test, the output shows an error:

NoSuchMethodError: Class 'DemoDouble' has no instance method 'doSomething' with matching arguments.
Receiver: Instance of 'DemoDouble'
Tried calling: doSomething(1)
Found: doSomething(int) => void

Expected behavior
I appreciate that the method is not stubbed, thus Mocktail is correctly reporting it to me. However, the following doesn't solve the problem:

when(_demo).calls(#doSomething);

but this one does:

when(_demo).calls(#doSomething).thenReturn(1);

Yet, this is a void function, so it feels wrong. Is there another way? What am I missing?

Cannot verify calls with unknown arguments

Describe the bug
using mocktail: ^0.0.1-dev.4
I'm experiencing difficulties to verify calls in a situation where I cannot provide the arguments with withArgs
getting the error below
NoSuchMethodError: Class 'MockErrorHandler' has no instance method 'call' with matching arguments.

To Reproduce

code sample
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

mixin Delegate<T> {
  T call();
}

mixin ErrorHandler {
  void call(Object e, StackTrace s);
}

class Foo {
  const Foo(this.errorHandler);
  final ErrorHandler errorHandler;
  void call(void Function() fun) {
    try {
      return fun();
    } catch (e, s) {
      errorHandler(e, s);
    }
  }
}

class MockErrorHandler extends Mock implements ErrorHandler {}

class MockDelegate<T> extends Mock implements Delegate<T> {}

void main() {
  final _errorHandler = MockErrorHandler();
  final _delegate = MockDelegate<String>();
  final _foo = Foo(_errorHandler);

  test('throw test', () {
    when(_delegate).calls('call').thenThrow('ops');
    _foo(_delegate);

    verify(_errorHandler).calls('call').times(equals(1));
  });
}

while seems like I cannot assign any to withArgs

 verify(_errorHandler).calls('call').withArgs(
      positional: [any, any], // <= `Undefined name 'any'. Try correcting the name to one that is defined, or defining the name.`
    ).times(equals(1));

Expected behavior

**Logs **

logs
00:14 +0 -1: throw test [E]                                                                                                                                                                                 
  NoSuchMethodError: Class 'MockErrorHandler' has no instance method 'call' with matching arguments.
  Receiver: Instance of 'MockErrorHandler'
  Tried calling: call("ops", #0      _StubFunction.thenThrow.<anonymous closure> (package:mocktail/src/mocktail.dart:290:48)
  #1      _Stub.result (package:mocktail/src/mocktail.dart:176:19)
  #2      Mock.noSuchMethod (package:mocktail/src/mocktail.dart:59:19)
  #3      MockDelegate.call (file:///home/francesco/projects/xxx/test/widget_test.dart:5:5)
  #4      Foo.call (file:///home/francesco/projects/xxxtest/widget_test.dart:17:17)
  #5      main.<anonymous closure> (file:///home/francesco/projects/xxx/test/widget_test.dart:38:9)
  #6      Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:200:19)
  <asynchronous suspension>
  #7      StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart)
  <asynchronous suspension>
  )
  Found: call(Object, StackTrace) => void
  dart:core                                 Object.noSuchMethod
  package:mocktail/src/mocktail.dart 65:18  Mock.noSuchMethod
  widget_test.dart 9:8                      MockErrorHandler.call
  widget_test.dart 19:19                    Foo.call
  widget_test.dart 38:9                     main.<fn>
  
00:14 +0 -1: Some tests failed.  
doctor
[βœ“] Flutter (Channel master, 1.26.0-2.0.pre.251, on Linux, locale en_US.UTF-8)
    β€’ Flutter version 1.26.0-2.0.pre.251 at /home/francesco/snap/flutter/common/flutter
    β€’ Framework revision 5bc725bea8 (8 hours ago), 2021-01-08 08:12:36 +0100
    β€’ Engine revision 2e5833b7c5
    β€’ Dart version 2.12.0 (build 2.12.0-179.0.dev)

[βœ“] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
    β€’ Android SDK at /home/francesco/Android/Sdk
    β€’ Platform android-30, build-tools 30.0.2
    β€’ ANDROID_SDK_ROOT = /home/francesco/Android/Sdk
    β€’ Java binary at: /usr/lib/jvm/java-1.8.0-openjdk-amd64/jre/bin/java
    β€’ Java version OpenJDK Runtime Environment (build 1.8.0_275-8u275-b01-0ubuntu1~20.04-b01)
    β€’ All Android licenses accepted.

[βœ“] Chrome - develop for the web
    β€’ Chrome at google-chrome

[βœ“] Linux toolchain - develop for Linux desktop
    β€’ clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
    β€’ cmake version 3.10.2
    β€’ ninja version 1.8.2
    β€’ pkg-config version 0.29.1

[!] Android Studio (not installed)
    β€’ Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/docs/get-started/install/linux#android-setup for detailed instructions).

[βœ“] Connected device (2 available)
    β€’ Linux (desktop) β€’ linux  β€’ linux-x64      β€’ Linux
    β€’ Chrome (web)    β€’ chrome β€’ web-javascript β€’ Google Chrome 87.0.4280.141

! Doctor found issues in 1 category.

Additional context
I just started using this package, so most likely is my fault, but for the 1% chance that it's an actual bug I'm reporting it

[NOT A BUG] I could use some help to understand this error

It's not a bug, I'm just having hard time to understand this error

To Reproduce

I'm not sure how to provide a repro code without dumping hundreds of lines of code.
The code below WORKS but is very close (in principle) and the mocks have the same identical implementation.

import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

mixin Delegate<T> {
  T call();
}

mixin ExceptionHandler {
  void call(Object e, StackTrace? s);
}

class MockExceptionHandler extends Mock implements ExceptionHandler {}

class MockDelegate<T> extends Mock implements Delegate<T> {}

void main() {
  final exceptionHandler = MockExceptionHandler();
  final delegate = MockDelegate<void>();
  when(exceptionHandler).calls(#call).thenAnswer((i) {});

  test('WHEN firstDelegate throws THEN `ExceptionHandler` call is called', () {
    when(delegate).calls(#call).thenThrow(Exception());
    try {
      delegate();
    } on Exception catch (e, s) {
      exceptionHandler(e, s);
    }

    verify(delegate).called(#call).once();
    verify(exceptionHandler).called(#call).once();
  });
}
name: mocktail_test
description: A new Flutter project.
environment:
  sdk: '>=2.12.0 <3.0.0'
  
dev_dependencies:
  test: ^1.16.5
  mocktail: ^0.0.0

**Logs **

this is the error that I struggle to understand

NoSuchMethodError: Class 'MockExceptionHandler' has no instance method 'call' with matching arguments.
Receiver: Instance of 'MockExceptionHandler'
Tried calling: call(Instance of '_Exception', #0      _StubFunction.thenThrow.<anonymous closure>
package:mocktail/src/mocktail.dart:565
#1      _Stub.result
package:mocktail/src/mocktail.dart:361
#2      Mock.noSuchMethod
package:mocktail/src/mocktail.dart:69
#3      MockUnaryDelegate.call
package:yak_runner/…/function/unary.dart:4
#4      YakRunnerArgAsync.call
package:yak_runner/…/async/unary.dart:33
<asynchronous suspension>
#5      StackZoneSpecification._registerUnaryCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart)
package:stack_trace/src/stack_zone_specification.dart:1
<asynchronous suspension>
)
Found: call(Exception, StackTrace?) => void

I honestly don't understand the difference between this
Receiver: Instance of 'MockExceptionHandler' Tried calling: call(Instance of '_Exception', #0
and this
Found: call(Exception, StackTrace?) => void

[βœ“] Flutter (Channel master, 1.27.0-9.0.pre.149, on Linux, locale en_US.UTF-8)
    β€’ Flutter version 1.27.0-9.0.pre.149 at /home/francesco/snap/flutter/common/flutter
    β€’ Framework revision 9964e8fe38 (7 hours ago), 2021-03-01 23:00:02 -0500
    β€’ Engine revision 17f6bfc480
    β€’ Dart version 2.13.0 (build 2.13.0-90.0.dev)

[βœ“] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
    β€’ Android SDK at /home/francesco/Android/Sdk
    β€’ Platform android-30, build-tools 30.0.2
    β€’ ANDROID_SDK_ROOT = /home/francesco/Android/Sdk
    β€’ Java binary at: /usr/lib/jvm/java-1.8.0-openjdk-amd64/jre/bin/java
    β€’ Java version OpenJDK Runtime Environment (build 1.8.0_282-8u282-b08-0ubuntu1~20.04-b08)
    β€’ All Android licenses accepted.

[βœ“] Chrome - develop for the web
    β€’ CHROME_EXECUTABLE = /snap/bin/chromium

[βœ“] Linux toolchain - develop for Linux desktop
    β€’ clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
    β€’ cmake version 3.10.2
    β€’ ninja version 1.8.2
    β€’ pkg-config version 0.29.1

[!] Android Studio (not installed)
    β€’ Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/docs/get-started/install/linux#android-setup for detailed instructions).

[βœ“] Connected device (2 available)
    β€’ Linux (desktop) β€’ linux  β€’ linux-x64      β€’ Linux
    β€’ Chrome (web)    β€’ chrome β€’ web-javascript β€’ Chromium 88.0.4324.182 snap

! Doctor found issues in 1 category.

I know it's OT as 99.9% not a bug
if you feel to close it go ahead, here's a SO question

More strict matchers for arguments

Describe the bug
Given the following class

class Foo {
  Future<int> asyncValueWithPositionalArg(int x) => Future.value(x);
}

and following test

    test('when asyncValueWithPositionalArg (explicit)', () async {
      when(foo)
          .calls('asyncValueWithPositionalArg')
          .thenAnswer((_) async => 10);
      expect(await foo.asyncValueWithPositionalArg(1), 10);
      verify(foo)
          .calls('asyncValueWithPositionalArg')
          .withArgs(positional: [1]).times(1);

    //this should fail but it succeeds
      verify(foo)
          .calls('asyncValueWithPositionalArg')
          .withArgs(positional: [2]).times(1);
    });

The last test should fail.

Expected behavior
I would expect that the last verification should fail. I did check the source code if I could create a fix for it but I was not able to. Also else it would be good to update the documentation that this use case is not supported.

[Suggestion] Change == override from Object to dynamic in Mock

Is your feature request related to a problem? Please describe.
When trying to mock certain object, their == override might use dynamic other instead of Object other, this will make mocking impossible because Mock.== uses Object.

An example of this is the Hive package, here is an example:

import 'package:hive/hive.dart';
import 'package:mocktail/mocktail.dart';

class MockBoxEvent extends Mock implements BoxEvent {}

The compiler will complain:

'Mock.==' ('bool Function(Object)') isn't a valid concrete implementation of 'BoxEvent.==' ('bool Function(dynamic)').

Describe the solution you'd like
Change

class Mock {
...

  @override
  bool operator ==(Object other) {
    ...
  }
}

To:

class Mock {
...

  @override
  bool operator ==(dynamic other) {
    ...
  }
}

Describe alternatives you've considered
Opening an issue into the other package, since Object is the right think to use. Which I did

Additional context
Once again I know using Object is the right thing to do. However since Mock sub-classes will never be sub-classed using the widest possible element (dynamic) might be better. I would understand if you don't want to though, I just wanted to let you know this could be an issue for some.

Also thanks so much for this package and the other you created (read bloc). I've been using your packages since I started flutter and they helped me to learn a lot. So thank you !

Future proofing

Is your feature request related to a problem? Please describe.
When we create a test double (stub, mock, other...) for a class which defines a method that returns a future, it is tempting to use Mocktail to return a canned future. This is a good approach to improve speed and reliability. Yet it must be arranged in a careful way to avoid complications.

Mocktail should prohibit developers to return a canned future with .thenReturn(). Instead, developers should be advised to use .thenAnswer().

Indeed, as Futures don't have a "start button", they run soon after they are instantiated. In the context of a canned future for tests, they are unfortunately likely to complete before our production code asks for the future.

Since await doesn't yield/block when applied to a completed future (it just unpacks the value), we write asynchronous code but run it synchronously from our tests. In short, we believe the production code is tested when we are actually testing a different program.

In my case, this led to many race conditions and an overcomplicated solution to address these race conditions.

Describe the solution you'd like
My suggestion is to throw an error from within .thenReturn() when the argument is of type Future. The error, may provide a brief explanation as to why it's a mistake, and a suggestion to use .thenAnswer() instead.

This is what I changed in a fork of your repository and have been running it successfully for a few days now. I did find that 42 out of ~150 tests were making this mistake.

Provided that in rare cases it is perhaps desirable to return a completed Future, Mocktail could provide a distinct method to allow this, even though it should be discouraged. Perhaps the name of that method will leave no doubts?

Describe alternatives you've considered
None besides being careful and consistent.

Additional context
The docs of SynchronousFuture warn us but are not very well connected to your library:

In general use of this class should be avoided as it is very difficult to debug such bimodal behavior.

I also assume that alternatives to Mocktail expose their users to a similar issue.

Throws type 'Null' is not a subtype of type 'Future<void>'

I think I found a bug, mostly with the error message throw that can causes confusion:

Consider the following classes:

class DataProvider {
  Future<void> justAFutureFunction() async {
    print('Ok, nothing wrong here');
  }
}

class JustARepository {
  final DataProvider innerRepo;

  JustARepository(this.innerRepo);

  Future<void> doSomethingWithTheRepository() async {
    await innerRepo.justAFutureFunction();
    print('Everything is ok');
  }
}

Now the following test that mocks the DataProvider call and everything works fine:

class MockInnerRepo extends Mock implements DataProvider {}

void main() {
  test('Test', () {
    final innerRepo = MockInnerRepo();
    final outerRepo = JustARepository(innerRepo);

    when(() => innerRepo.justAFutureFunction())
        .thenAnswer((invocation) => Future.value());

    expect(outerRepo.doSomethingWithTheRepository(), completes);
  });
}

Now if you add a second when call, that mocks the method from the repository, we get the error:
type 'Null' is not a subtype of type 'Future<void>:

class MockInnerRepo extends Mock implements DataProvider {}

void main() {
  test('Test', () {
    final innerRepo = MockInnerRepo();
    final outerRepo = JustARepository(innerRepo);

    when(() => innerRepo.justAFutureFunction())
        .thenAnswer((invocation) => Future.value());

    /// This code causes the test to break
    when(() => outerRepo.doSomethingWithTheRepository())
        .thenAnswer((invocation) => Future.value());

    expect(outerRepo.doSomethingWithTheRepository(), completes);
  });
}

I know that calling a method from something that is not being mocked sounds stupid, but this code above actually works with mockito. I spend about 2 hours trying to figure out what I was doing wrong and the error was the way I wrote thet test itself. But like I said, everything was passing with mockito.

Maybe telling the user that we should mock the Repository before trying to call when on it?

Error: Was a real method called, or perhaps an extension method?

Hello folks its me again!
After our talk about creating an extensions library (#46) I started the project and faced the first problem of this approach:
I moved the extension function to inside my project like that:

extension VoidAnswer on When<Future<void>> {
  /// Store a function which is called when this method stub is called.
  ///
  /// The function will be called and completed normally.
  void thenAnswerWithVoid() => thenAnswer((_) => Future<void>.value());

  //void thenAnswerWithVoid() => _completeWhen((_) => Future<void>.value());
}

Please note that I had to change the call as it was in the PR, because the method _completeAnswer is private in mocktail, so we can't call it directly.

void _completeWhen(Answer<T> answer) {
    if (_whenCall == null) {
      throw StateError(
        'No method stub was called from within `when()`. Was a real method '
        'called, or perhaps an extension method?',
      );
    }
    _whenCall!._setExpected<T>(answer);
    _whenCall = null;
    _whenInProgress = false;
  }

It throws Bad state: No method stub was called from within `when()`. Was a real method called, or perhaps an extension method?

Is there any workaround can you folks can think to overcome that issue?

Cubit Testing: Bad state: A test tried to use `any` or `captureAny` on a parameter of type `T State`, but registerFallbackValue was not previously called to register a fallback value for `T State`

Describe the bug
I'm migrating one personal project to the latest versions and null safety. But I'm having problems doing widget testing on the latest version with mocktail and cubit.

**Test I'm trying to run **

import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

import 'package:qr_generator/qr_generator.dart';
import 'package:qr_generator_flutter/src/features/qr_generator/logic/qr_generator_cubit.dart';
import 'package:qr_generator_flutter/src/features/qr_generator/logic/qr_generator_state.dart';
import 'package:qr_generator_flutter/src/features/qr_generator/views/qr_generator_page.dart';
import 'package:qr_generator_flutter/src/features/qr_generator/views/qr_generator_page_i18n.dart';
import 'package:qr_generator_flutter/src/features/qr_scanner/logic/qr_scanner_cubit.dart';
import 'package:qr_generator_flutter/src/features/qr_scanner/logic/qr_scanner_state.dart'
    as scanner_s;
import 'package:qr_generator_flutter/src/features/qr_scanner/views/qr_scanner_page.dart';
import 'package:qr_generator_flutter/src/features/qr_generator/views/widgets/qr_code_widget.dart';

class MockGetSeed extends Mock implements GetSeed {}

class MockQrGeneratorCubit extends MockCubit<QrGeneratorState>
    implements QrGeneratorCubit {}

class MockQrScannerCubit extends MockCubit<scanner_s.QrScannerState>
    implements QrScannerCubit {}

void main() {
  group('QrGeneratorPage', () {
    final tSeed = Seed(
      seed: 'seed',
      expiresAt: DateTime.now().add(const Duration(seconds: 15)),
    );

    late QrGeneratorCubit qrGeneratorCubit;
    late QrScannerCubit qrScannerCubit;

    setUp(() {
      qrGeneratorCubit = MockQrGeneratorCubit();
      qrScannerCubit = MockQrScannerCubit();
    });

    testWidgets('renders a QrGeneratorPage', (tester) async {
      ///arrange
      when(() => qrGeneratorCubit.state)
          .thenReturn(const QrGeneratorState.initial());

      ///act
      await tester.pumpWidget(BlocProvider.value(
        value: qrGeneratorCubit,
        child: const MaterialApp(home: QrGeneratorPage()),
      ));

      ///expect
      expect(find.byKey(const Key('kBodyKey')), findsOneWidget);
    });
  });
}

Error I'm getting

//Bad state: A test tried to use `any` or `captureAny` on a parameter of type `QrGeneratorState`, but
//registerFallbackValue was not previously called to register a fallback value for `QrGeneratorState`

To fix, do:


void main() {
  setUpAll(() {
    registerFallbackValue<QrGeneratorState>(QrGeneratorState());
  });
}


//If you cannot easily create an instance of QrGeneratorState, consider defining a `Fake`:


class QrGeneratorStateFake extends Fake implements QrGeneratorState {}

void main() {
  setUpAll(() {
    registerFallbackValue<QrGeneratorState>(QrGeneratorStateFake());
  });
}

I already tried doing what the error message says, but I'm still getting the same error.

What's the intended behavior for methods that can be tear-offs?

Describe the bug

Hey! πŸ‘‹πŸ»

Love this project.

When I write when or verify with a method that takes no args and can make a tear-off, I get a complaint from the analyzer because I have the tear-off warning enabled.

image

I'm curious what you think. Is it safe to use a tear-off? It seems to work fine.

How to pass arguments to mock constructor + mock internal method or work around it

Describe the bug
Mocked constructor is not taking arguments and I cannot mock internal methods if using a real class with mocked repository passed in the constructor.

To Reproduce
First of all, thank you for the great plugin, this is not even a bug more like a question probably and help for a workaround if it's not fixable directly.

I have a class that takes repository in constructor argument because I have all 3rd party connections there I want to pass it as a mock to stub everything, but first of all Mock class does not care about constructor and I simply cannot pass to it arguments or override it, I can use real class and pass mock repository its fine, but what if I want to additionally mock other methods of this class because it's not extending mock I am not able to mock another internal method and fake class will ask for a lot of code duplications.

import 'package:mocktail/mocktail.dart';
// I removed some types to just show the point
// A Real Cat class
class Cat {
  final repositoryClassThatNeedsToBeMocked; // should be mocked
  Cat(this.repositoryClassThatNeedsToBeMocked); // want to pass mocked repository
  
  functionForTest() {
      repositoryClassThatNeedsToBeMocked.someFunctionWhichIWantToMock(); // should be mocked
      /* 
        some logic that I want to test...
      */
      heavyInternalFunctionToMock(); // should be mocked
   };

   heavyInternalFunctionToMock() => {}; // i want to mock it also 
}

// A Mock Cat class
class MockCat extends Mock implements Cat {} // here besides constructor is required and needs repositoryClassThatNeedsToBeMocked, he is not asking for it and override is not working either.

Expected behavior
Not sure how complex it is to integrate arguments in the mocked constructor but it will solve a lot of issues and make design clear, otherwise maybe some kind of spy implementation, etc, or design workaround tips are appreciated

MockNavigationObserver

Hi I am trying to add a test to Navigate from one page to another and hence am using NavigationObserver
But I am unable to make it work as it says
registerFallbackValue was not previously called to register a fallback value for Route<dynamic>flutter.
Could you Please give a sample test for this scenario

`when` method throwing error `type 'Null' is not a subtype of type ...`

Description
I am trying to implement Reso Coder's Clean Architecture in my own project and have come across this problem while testing. Using the when method from Mocktail would throw out the error: type 'Null' is not a subtype of type 'Future<Either<Failure, List<WorkoutEntity>>>'. A minimal reproduction will be given below.

I've confirmed it's not a bloc_test issue as the last test still does not pass.

Steps To Reproduce

  1. flutter create bug
  2. Copy and paste the following 'pubspec.yaml' file:
pubspec.yaml
name: bug
description: A new Flutter project.

publish_to: 'none'

version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter
  # Functional programming
  dartz: ^0.10.0-nullsafety.2
  # BLoC (State management)
  flutter_bloc: ^7.1.0
  # Boilerplate code
  equatable: ^2.0.3

dev_dependencies:
  flutter_test:
    sdk: flutter
  # Testing for BLoC
  bloc_test: ^8.1.0
  # Mock testing
  mocktail: ^0.1.4

# The following section is specific to Flutter.
flutter:
  uses-material-design: true
  1. Copy and paste the following file into the 'test' directory:
workouts_bloc_test.dart
import 'package:bloc_test/bloc_test.dart';
import 'package:bug/failures.dart';
import 'package:bug/use_case.dart';
import 'package:bug/workout_entity.dart';
import 'package:bug/workouts_bloc.dart';
import 'package:bug/workouts_event.dart';
import 'package:bug/workouts_state.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class MockGetAllWorkouts extends Mock implements GetAllWorkouts {}

void main() {
  late MockGetAllWorkouts mockGetAllWorkouts;

  setUp(() {
    mockGetAllWorkouts = MockGetAllWorkouts();
  });

  /// Create a workout
  WorkoutEntity tWorkout = WorkoutEntity();

  group('GetAllWorkouts', () {
    List<WorkoutEntity> workouts = [tWorkout];

    blocTest<WorkoutsBloc, WorkoutsState>(
      'should emit [WorkoutsLoadFailure] when loading workouts fails',
      build: () {
        when(() async => mockGetAllWorkouts(NoParams()))
            .thenAnswer((_) async => Left(ReadFailure()));

        return WorkoutsBloc(
          getAllWorkouts: mockGetAllWorkouts,
        );
      },
      act: (bloc) => bloc.add(WorkoutsLoaded()),
      expect: () => [isA<WorkoutsLoadFailed>()],
    );

    blocTest<WorkoutsBloc, WorkoutsState>(
      'should emit [WorkoutsLoadSuccess] when loading workouts succeeds',
      build: () {
        when(() async => mockGetAllWorkouts(NoParams()))
            .thenAnswer((_) async => Right(workouts));

        return WorkoutsBloc(
          getAllWorkouts: mockGetAllWorkouts,
        );
      },
      act: (bloc) => bloc.add(WorkoutsLoaded()),
      expect: () => [isA<WorkoutsLoadSuccess>()],
    );
  });

  test(
    'GetAllWorkouts should emit [WorkoutsLoadFailure] when loading workouts fails',
    () async {
      // Arrange
      when(() async => mockGetAllWorkouts(NoParams()))
          .thenAnswer((_) async => Left(ReadFailure()));

      final bloc = WorkoutsBloc(
        getAllWorkouts: mockGetAllWorkouts,
      );

      // Assert later
      final expected = [
        WorkoutsLoadInProgress(),
        WorkoutsLoadFailed(message: ''),
      ];
      expectLater(bloc.stream, emitsInOrder(expected));

      // Act
      bloc.add(WorkoutsLoaded());
    },
  );
}
  1. Copy and paste the following files into the 'lib' directory:
failures.dart
import 'package:equatable/equatable.dart';

abstract class Failure extends Equatable {
  @override
  List<Object?> get props => [];
}

class ReadFailure extends Failure {}
i_workout_repository.dart
import 'package:dartz/dartz.dart';

import 'failures.dart';
import 'workout_entity.dart';

abstract class IWorkoutRepository {
  /// Gets all [WorkoutEntity] from the database.
  ///
  /// Returns [List<WorkoutEntity>].
  Future<Either<Failure, List<WorkoutEntity>>> getAllWorkoutEntities();
}
use_case.dart
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';

import 'failures.dart';
import 'i_workout_repository.dart';
import 'workout_entity.dart';

// Parameters have to be put into a container object so that they can be
// included in this abstract base class method definition.
abstract class UseCase<Type, Params> {
  Future<Either<Failure, Type>> call(Params params);
}

class NoParams extends Equatable {
  @override
  List<Object> get props => [];
}

class GetAllWorkouts implements UseCase<List<WorkoutEntity>, NoParams> {
  final IWorkoutRepository repository;

  const GetAllWorkouts(this.repository);

  @override
  Future<Either<Failure, List<WorkoutEntity>>> call(NoParams params) async {
    return await repository.getAllWorkoutEntities();
  }
}
workout_entity.dart
import 'package:equatable/equatable.dart';

class WorkoutEntity extends Equatable {
  @override
  List<Object?> get props => [];
}
workouts_bloc.dart
import 'package:flutter_bloc/flutter_bloc.dart';

import 'use_case.dart';
import 'workouts_event.dart';
import 'workouts_state.dart';

const String WORKOUT_LOAD_FAILURE_MESSAGE =
    'Workouts failed to load. Are there new fields present in [WorkoutEntity]?';

class WorkoutsBloc extends Bloc<WorkoutsEvent, WorkoutsState> {
  final GetAllWorkouts getAllWorkouts;

  WorkoutsBloc({
    required this.getAllWorkouts,
  }) : super(WorkoutsLoadInProgress());

  @override
  Stream<WorkoutsState> mapEventToState(
    WorkoutsEvent event,
  ) async* {
    if (event is WorkoutsLoaded) {
      yield* _mapWorkoutsLoadedToState();
    }
  }

  Stream<WorkoutsState> _mapWorkoutsLoadedToState() async* {
    final workoutsEither = await getAllWorkouts(NoParams());

    yield workoutsEither.fold(
      (failure) => WorkoutsLoadFailed(message: WORKOUT_LOAD_FAILURE_MESSAGE),
      (workouts) => WorkoutsLoadSuccess(workouts: workouts),
    );
  }
}
workouts_events.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

@immutable
abstract class WorkoutsEvent extends Equatable {
  const WorkoutsEvent();

  @override
  List<Object> get props => [];
}

/// Load all workouts.
class WorkoutsLoaded extends WorkoutsEvent {}

workouts_state.dart
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

import 'workout_entity.dart';

@immutable
abstract class WorkoutsState extends Equatable {
  const WorkoutsState();

  @override
  List<Object> get props => [];
}

class WorkoutsLoadInProgress extends WorkoutsState {}

class WorkoutsLoadSuccess extends WorkoutsState {
  final List<WorkoutEntity> workouts;

  const WorkoutsLoadSuccess({required this.workouts});

  @override
  List<Object> get props => [workouts];

  @override
  String toString() => 'WorkoutsLoaded { workouts: $workouts }';
}

class WorkoutsLoadFailed extends WorkoutsState {
  final String message;

  const WorkoutsLoadFailed({required this.message});

  @override
  List<Object> get props => [message];
}
  1. flutter test
  2. See errors

Expected Behavior
Expected: Mocked function correctly outputs.
Actual: Tests cannot proceed due to the type Null... error.

dartargument_type_not_assignable -> How to make a Mock of an http.Client simulating a get?

I'm using the package:

  • mocktail: ^0.1.2

How to make a Mock of an http.Client simulating a get?

When trying to execute:

when(clientMock).calls(#get).thenAnswer((invocation) async => http.Response(jsonReturn, 200));

It presents the following error:
The argument type 'ClientMock' can't be assigned to the parameter type 'dynamic Function()'.dartargument_type_not_assignable

I tried the following code too:

when((_) => clientMock.get(any())).thenAnswer((invocation) async => http.Response(jsonReturn, 200));

It presents the following error:
The argument type 'Future Function(dynamic)' can't be assigned to the parameter type 'dynamic Function()'.


Code


import 'dart:convert';
import 'package:meta/meta.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';
import 'package:http/http.dart';

class ProdutoEntity {
  int id;
  int dataCadastro;
  String descricao;
  double precoCompra;
  double precoVenda;
  double desconto;

  ProdutoEntity({
    this.id,
    this.dataCadastro,
    this.descricao,
    this.precoCompra,
    this.precoVenda,
    this.desconto
  });

  ProdutoEntity.fromJson(Map<String, dynamic> json) {
    id = int.parse(json['id']);
    dataCadastro = json['data_Cadastro'];
    descricao = json['descricao'];
    precoCompra = json['preco_compra'] * 1.0;
    precoVenda = json['preco_venda'] * 1.0;
    desconto = json['desconto'] * 1.0;
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['id'] = this.id;
    data['data_Cadastro'] = this.dataCadastro;
    data['descricao'] = this.descricao;
    data['preco_compra'] = this.precoCompra;
    data['preco_venda'] = this.precoVenda;
    data['desconto'] = this.desconto;
    return data;
  }
}

class ProdutoRepository {
  final Client client;

  ProdutoRepository({
    @required this.client,
  });

  Future<List<ProdutoEntity>> getProdutos() async {
    final response = await client.get(Uri.parse("https://608dd130fe2e9c00171e1fea.mockapi.io/api/v1/produtos"));

    if (response.statusCode == 200) {
      final jsonList = jsonDecode(response.body) as List;
      return jsonList.map((e) => ProdutoEntity.fromJson(e)).toList();
    } else {
      throw Exception("Erro da ConexΓ£o: ${response.statusCode}");
    }
  }
}

const jsonReturn = '''
    [
      { "id":"1", "data_Cadastro":1620055946, "descricao":"XXXX", "preco_compra":70, "preco_venda":51, "desconto":92 },
      { "id":"1", "data_Cadastro":1620055946, "descricao":"YYYY", "preco_compra":70, "preco_venda":51, "desconto":92 }
    ]
    ''';

void main() {
  @Timeout(Duration(
    seconds: 60,
  ))
  @Tags([])
  ClientMock clientMock;

  setUp(() async {
    clientMock = ClientMock();
  });

  test(
    'Receber Lista de Produtos',
    () async {
      when(clientMock).calls(#get).thenAnswer((invocation) async => http.Response(jsonReturn, 200));

      // final repository = ProdutoRepository(client: clientMock);
      // final list = await repository.getProdutos();

      // expect(list.isNotEmpty, equals(true));
      // expect(list.length, greaterThan(1));
      // expect(list.first.descricao, equals("XXXX"));
    },
    tags: [],
  );

  tearDown(() {});
}

Question: Behavior while verifying generic parameters types. Is this expected?

Hello,

I am trying to use mocktail in a pure dart lib however, I am getting a weird behavior when verifying a call with a generic parameter type.

I have a simplified version of DartZ/Either, with names changed and just the fold and check type methods.

abstract class Result<Exception, S> {...}
class Failure<Exception, S> extends Result<Exception, S> {...}
class Success<Exception, S> extends Result<Exception, S> {...}

Then I want to check if the output class was called with one of the specified types (Success or Failure).

I first add the fallbacks and set up the mocks

  final genericException = Exception('ERROR');
  // Output parameters
  final outputParameterSuccess = Success<Exception, CatOutputDTO>(
      CatOutputDTO());
  final outputParameterFailure =
      Failure<Exception, CatOutputDTO>(genericException);

  setUpAll(() {
    // fallback values for output call
    registerFallbackValue(outputParameterSuccess);
    registerFallbackValue(outputParameterFailure);
  });

  setUp(() {
    mockedRepository = MockCatRepository();
    output = MockGetCatOutput();
    useCase = GetCatUseCase(mockedRepository, output);
  });

The failure test:

  test('Should call the output with Failure if the repository returned Failure',
      () async {
    // Arrange / Given
    // stub the repository call, returning a Failure
    when(() => mockedRepository.getCat())
        .thenAnswer((_) async => repositoryResultFailure);
    // stub the output call, return true (meaning executed with success);
    when(() => output.call(any())).thenAnswer((_) async => true);

    // Act / When
    await useCase.call();

    // Assert / Expect
    // Verify the output was called once
    verify(() => output.call(outputParameterFailure)).called(1);
  });

This works fine, since the repository is returning a Failure, the output should be called with a Failure, for example, if I change the verify from outputParameterFailure to outputParameterSuccess, it fails, since the type doesn't match the call made.

The return from the output doesn't matter for me, just the parameter being passed.

Now, this gets weird when I try to test the Success case, when the repository returns a Success, my useCase should call the output with a Success too. So:

  test('Should call the output with Success if the repository returned Success',
      () async {
    // Arrange / Given
    // stub the repository call, returning a Failure
    when(() => mockedRepository.getCat())
        .thenAnswer((_) async => repositoryResultSuccess);
    // stub the output call with anything
    when(() => output.call(any())).thenAnswer((_) async => true);

    // Act / When
    await useCase.call();

    print("outputParameterSuccess: " + outputParameterSuccess.toString());

    // Assert / Expect
    // Verify the output was called once
    verify(() => output.call(outputParameterSuccess)).called(1);
  });

The error I get is the weird thing itself.

00:01 +2: test/usecases/get_cat_test.dart: Should call the output with Success if the repository returned Success                                                                
outputParameterSuccess: Success(Instance of 'CatOutputDTO')
00:01 +2 -1: test/usecases/get_cat_test.dart: Should call the output with Success if the repository returned Success [E]                                                         
  No matching calls. All calls: MockGetCatOutput.call(Success(Instance of 'CatOutputDTO'))
  (If you called `verify(...).called(0);`, please instead use `verifyNever(...);`.)
  package:test_api                                      fail
  package:mocktail/src/mocktail.dart 723:7              _VerifyCall._checkWith
  package:mocktail/src/mocktail.dart 516:18             _makeVerify.<fn>
  test/usecases/get_cat_test.dart 92:11  main.<fn>

The UseCase:

class GetCatUseCase implements GetCat {
  final CatDataAccess catDataAccess;
  final GetCatOutput output;

  GetCatUseCase(this.catDataAccess, this.output);

  @override
  Future<bool> call() async {
    var result = await catDataAccess.getCat();
    await result.fold(
      (failure) async => await output.call(Failure(failure)),
      (success) async => await output.call(Success(success.parseAsOutputDTO())),
    );
    return result.isSuccess();
  }
}

The use case is calling the output correctly, and even the output confirms it at All calls: MockGetCatOutput.call(Success(Instance of 'CatOutputDTO')). The print output placed there, also outputs the same instance type. However, the test fails.

I suspect the motive behind the test passing with Failure is because Failure(Exception) isn't generic, and Success(T) is? Maybe I am doing something wrong or missing something important?

I also suspect a bit about the Result<Exception, CatOutputDTO> type expected when veryfing with verify(() => output.call(outputParameterSuccess)).called(1);... It might be expecting the Result<Exception, CatOutputDTO>, but instead getting the Success(Instance of 'CatOutputDTO') ? It's a little weird for me.

The Output class is structure as follow:

class MockGetCatOutput extends Mock
    implements GetCatOutput {}

abstract class GetCatOutput
    implements OutputBoundary<CatOutputDTO> {}

abstract class OutputBoundary<SuccessType> {
  Future<bool> call(Result<Exception, SuccessType> result);
}

Thank you all in advance.

Small typo in error message

What's written

Bad state: A test tried to use `any` or `captureAny` on a parameter of type `RefreshAccountSnapshotOptions`, but
registerFallbackValue was not previously called to register a fallback value for `RefreshAccountSnapshotOptions`

To fix, do:

void main() {
  setupAll(() {
    registerFallbackValue<RefreshAccountSnapshotOptions>(RefreshAccountSnapshotOptions());
  });
}

What's should be written

setUpAll instead of setupAll.

Note

Yeah I know it's just a small thing. But an inconsistency can feels somewhat troubling for a perfectionist. lol

Should I submit a PR for this?

Verify does not seem to work with named arguments

Describe the bug
Verify does not seem to work with named arguments

To Reproduce
My code:

test('should authorized http client', () async {
  bool hasAuthorized = false;
  when(
    () => mockHttp.authorize(
      username: any(named: 'username'),
      password: any(named: 'password'),
      options: any(named: 'options'),
    ),
  ).thenAnswer((i) {
    if (i.namedArguments[const Symbol('username')] == fakeUsername &&
        i.namedArguments[const Symbol('password')] == fakePassword &&
        i.namedArguments[const Symbol('options')] == fakeOptions) {
      hasAuthorized = true;
    }
    return mockAuthorizedHttp;
  });

  await api.getStuff();

  expect(hasAuthorized, isTrue); // this succeeds
  verify(
    () => mockHttp.authorize(
      username: fakeUsername,
      password: fakePassword,
      options: fakeOptions,
    ),
  ); // but this fails
});

Expected behavior
The verify should succeed

**Logs **
Paste the output of running flutter doctor -v here.

[βœ“] Flutter (Channel stable, 2.2.0, on Microsoft Windows [Version 10.0.19042.985], locale en-BE)
    β€’ Flutter version 2.2.0 at C:\tools\flutter
    β€’ Framework revision b22742018b (11 days ago), 2021-05-14 19:12:57 -0700
    β€’ Engine revision a9d88a4d18
    β€’ Dart version 2.13.0

[βœ“] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    β€’ Android SDK at C:\Users\wvl\AppData\Local\Android\sdk
    β€’ Platform android-30, build-tools 30.0.3
    β€’ Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
    β€’ Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)
    β€’ All Android licenses accepted.

[βœ“] Chrome - develop for the web
    β€’ Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[βœ“] Visual Studio - develop for Windows (Visual Studio Community 2019 16.9.4)
    β€’ Visual Studio at C:\Program Files (x86)\Microsoft Visual Studio\2019\Community
    β€’ Visual Studio Community 2019 version 16.9.31205.134
    β€’ Windows 10 SDK version 10.0.19041.0

[βœ“] Android Studio (version 4.1.0)
    β€’ Android Studio at C:\Program Files\Android\Android Studio
    β€’ Flutter plugin can be installed from:
      πŸ”¨ https://plugins.jetbrains.com/plugin/9212-flutter
    β€’ Dart plugin can be installed from:
      πŸ”¨ https://plugins.jetbrains.com/plugin/6351-dart
    β€’ Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b01)

[βœ“] IntelliJ IDEA Ultimate Edition (version 2021.1)
    β€’ IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.2
    β€’ Flutter plugin version 56.0.5
    β€’ Dart plugin version 211.7233

[βœ“] VS Code, 64-bit edition (version 1.56.0)
    β€’ VS Code at C:\Program Files\Microsoft VS Code
    β€’ Flutter extension version 3.18.1

[βœ“] Connected device (3 available)
    β€’ Windows (desktop) β€’ windows β€’ windows-x64    β€’ Microsoft Windows [Version 10.0.19042.985]
    β€’ Chrome (web)      β€’ chrome  β€’ web-javascript β€’ Google Chrome 90.0.4430.212
    β€’ Edge (web)        β€’ edge    β€’ web-javascript β€’ Microsoft Edge 90.0.818.56

β€’ No issues found!

verifyInOrder not working and no example test

Describe the bug
I tried migrating some simple mockito tests and got pretty much everything working except my verifyInOrder([]) calls.

I am not sure what to pass as arguments there. I tried passing the calls directly and also as closure but neither of these approaches worked.

Invalid implementation override when using Fake with ThemeData and ColorScheme

Describe the bug
When using Fake with ThemeData and ColorScheme, I'm getting an invalid_implementation_override error with the following message:

'Object.toString' ('String Function()') isn't a valid concrete implementation of 'Diagnosticable.toString' ('String Function({DiagnosticLevel minLevel})')

To Reproduce

class FakeThemeData extends Fake implements ThemeData {}

class FakeColorScheme extends Fake implements ColorScheme {}

Expected behavior
I should be able to use Fake with ThemeData and ColorScheme

Screenshots
N/A

Logs
N/A

Additional context
N/A

'Null' is not a subtype of type 'Future<void>'

Hi, I'm migrating some app to null safety and I found this package to be easier to use than mockito, but now I'm having this issue:
'Null' is not a subtype of type 'Future'

I have this abstract class:
import 'dart:async';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

abstract class Prefs {

Future getString(String key);

Future setString(String key, String s);
}
// the implementation
class SecureStoragePrefs implements Prefs {
static final storage = new FlutterSecureStorage();

Future getString(String key) async {
String? value = await storage.read(key: key);
return value ?? "";
}

Future setString(String key, String s) async {
storage.write(key: key, value: s);
}
}

My viewmodel:

class LogoutViewModel {
Prefs prefs;

LogoutViewModel(this.prefs);

Future logout() async {
await prefs.setString("user.prefs", "");
}
}

And the test:

`import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class MockPrefs extends Mock implements Prefs {}

void main() {

late LogoutViewModel logoutViewModel;
late Prefs prefs;

setUp(() {
prefs = MockPrefs();
logoutViewModel = new LogoutViewModel(prefs);
});

testWidgets('should clear all user data', (WidgetTester tester) async {
logoutViewModel.logout();

verify(() => prefs.setString("user.prefs", ""));

});
}`

Since prefs.setStrig returns Future, why am I receiving this error?

Support using `Mock` as mixin

Is your feature request related to a problem? Please describe.
I'm using mocktail in conjunction with getx and want do use the Mock class as a mixin:

class MockBluetoothService extends GetxService
    with Mock
    implements BluetoothService {}

However, the prefer_mixin rule tells me that Mock should be a mixin, not a class. I've disabled the rule for now.

Describe the solution you'd like
It would be awesome if mocktail provided a MockMixin or similar that was tailor-made to be used as a mixin.

Describe alternatives you've considered
Alternatives are to disable the prefer_mixin rule. I don't think I can use GetxService as a mixin.

we can't define a stub on an object that we want to mock before constructing it, but the constructor for the mock prevents us from stubbing the public member

DiagnosticsScreenBloc({
    required this.myDiagnosticsListBloc,
  }) : super(DiagnosticsScreenState.initialize()) {
    myDiagnosticsListSubscription = myDiagnosticsListBloc.stream.listen(
      (state) {
        // some code
    );
    
This is how constructor of tested class looks like.

 setUp(
() {
  myDiagnosticsListBloc = MockMyDiagnosticsListBloc();

  when(() => myDiagnosticsListBloc.stream).thenAnswer(
    (_) => Stream<MyDiagnosticsListState>.fromIterable([]),
  );

  diagnosticsScreenBloc = DiagnosticsScreenBloc(
    myDiagnosticsListBloc: myDiagnosticsListBloc,

  );

},

);

Bad state: No method stub was called from within when(). Was a real method called, or perhaps an extension method?
LateInitializationError: Local 'diagnosticsScreenBloc' has not been initialized.

Those are the errors I get when I try to run the tests.

If i remove this when from setup I get this error
type 'Null' is not a subtype of type 'Stream' in type cast

This is how I generate mock for MyDiagnosticsListBloc

@GenerateMocks([
], customMocks: [
  MockSpec<MyDiagnosticsListBloc>(returnNullOnMissingStub: true),
])

Is there any way you can help me solve this ?

Using when() cause The argument type 'Either<Failure, int>' can't be assigned to the parameter type 'dynamic Function()'

Describe the bug
I want to stub my mock method using when(), but it show error

To Reproduce
Steps to reproduce the behavior:

  1. Here is my class
    class InputConverter { Either<Failure, int> stringToUnsignedInteger(String str) { int? val = int.tryParse(str); if (val == null || val.isNegative) { return Left(InvalidInputFailure()); } return Right(val); } }
  2. Create mock and create instance of it

class MockInputConverter extends Mock implements InputConverter {}

  1. create stub
    when(mockInputConverter.stringToUnsignedInteger(any)) .thenReturn(Right(numberParsed));

Expected behavior
I expect it has no error, it work previously if using mockito

i am using mocktail: ^0.2.0

Flutter (Channel stable, 2.5.3, on macOS 11.6 20G165 darwin-x64, locale en-ID)
β€’ Flutter version 2.5.3 at /Users/diazfebrian/development/flutter
β€’ Upstream repository https://github.com/flutter/flutter.git
β€’ Framework revision 18116933e7 (2 weeks ago), 2021-10-15 10:46:35 -0700
β€’ Engine revision d3ea636dc5
β€’ Dart version 2.14.4

mocktail 0.0.2-dev.5 is incompatible with test >=1.16.7

Describe the bug
The issue is caused by having both mocktail 0.0.2-dev.5 and test 1.16.7 in pubspec.yaml

Because mocktail 0.0.2-dev.5 depends on test_api ^0.2.19 and test >=1.16.7 depends on test_api 0.3.0, mocktail 0.0.2-dev.5 is incompatible with test >=1.16.7.
So, because mbee depends on both test 1.16.7 and mocktail 0.0.2-dev.5, version solving failed.
pub get failed (1; So, because mbee depends on both test 1.16.7 and mocktail 0.0.2-dev.5, version solving failed.)

Why is there no built-in fallback value for DateTime?

Describe the bug
I wrote a test that used a mock with a method accepting a DateTime and I got the error that I need to register a fallback value for DateTime.

Expected behavior
I was expecting that there'd be a fallback value already registered since DateTime is a commonly used built-in type.

I'm curious what you think!

Cheers

How to bypass limitations of constructor inside extension without changing public API?

  1. I have a library, which use a 3rd party class. The 3rd party library is used by users directly together with my library.
    Below is the source code in my library.
/// Cannot modify this class because it's from a 3rd party library.
/// This class is visible to users and they are using it.
class ThirdPartyCat {
  const ThirdPartyCat();
}

/// This implementation works but cannot be mocked.
extension CatExt on ThirdPartyCat {
  // Public API method
  PublicDog get dog => PublicDog(this);
}

// Public API class
class PublicDog {
  const PublicDog(this.cat);
  
  // Public API member
  final ThirdPartyCat cat;
}

void main() {
  const cat = ThirdPartyCat();
  cat.dog; // works
}
  1. Because we cannot mock extension and constructor directly, I change the implementation above like this:
/// Cannot modify this class because it's from a 3rd party library.
/// This class is visible to users and they are using it.
class ThirdPartyCat {
  const ThirdPartyCat();
}

/// Mocking extension and constructor is not possible => use a helper class.
/// Note that, this class is not for users. Users still use only ThirdPartyLibCat.
abstract class CatHelper extends ThirdPartyCat {
  PublicDog createDog(PublicDog dog) => dog;
}

extension CatExt on CatHelper {
  // Public API method
  PublicDog get dog => createDog(PublicDog(this));
}

// Public API class
class PublicDog {
  const PublicDog(this.cat);
  
  // Public API member => don't allow users to see CatHelper.
  final ThirdPartyCat cat;
}

void main() {
  const cat = ThirdPartyCat();

  // Now we can mock createDog method, therefore indirectly mock extension method and PublicDog instance.
  // But with compile time error: The getter 'dog' isn't defined for the type 'ThirdPartyLibCat'.
  cat.dog;
}

The solution above allow mocking but will break the current public API.

Do you have any suggestion in this case?

Documentation: Throws type 'Null' is not a subtype of type 'SomeType'

Hi,

I greatly appreciate the work that you are doing for the fluttter community. Especially since the null-safety and creating a better tool than mockito.

I ran into the following issue when porting from mockito to mocktail, whereas mockito somehow skipped non-mocked methods because of being null.

I received this error:

Throws type 'Null' is not a subtype of type 'Future<void>'

Now it took me a while to figure out what was happening, so maybe you could add a small paragraph explaining what to do when hitting this error when porting your mockito code to mocktail.

Eg. something like this:

When you receive the following error:

Throws type 'Null' is not a subtype of type 'SomeType'

This error means you need to add a when(() => mock.missingMockMethod()).thenAnswer((_) => yourAnswer) on that particular method of your mock you are using, since mockito kindly skipped this for you because it was null in the pre-null-safety era.

Verify counts all extension functions instead of the specified one

Describe the bug
When using verify on an extension function, it failed to matches the specified function but instead matches all the functions defined in the extension. As a result, the also failed to match the specified count value.

I have a reproducible example with flutter_bloc cubits and I'm not 100% sure if this is an issue related to mocktail or flutter_bloc. Given that you maintain both packages, maybe you can help me to figure out πŸ™‚

To Reproduce
Below are the related classes/files to reproduce the issue:

CounterCubit
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';

part 'counter_state.dart';

class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState());
}

extension CounterExt on CounterCubit {
void add() => emit(state.copyWith(state.count + 1));

void subtract() => emit(state.copyWith(state.count - 1));
}
CounterState
part of 'counter_cubit.dart';

class CounterState extends Equatable {
final int count;

const CounterState([this.count = 0]);

@override
List<Object> get props => [count];

CounterState copyWith([int? count]) {
  return CounterState(count ?? this.count);
}
}
CounterWidget
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class Counter extends StatelessWidget {
const Counter({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
  return Column(
    children: [
      ElevatedButton(
        key: const Key('counter_add'),
        onPressed: () => context.read<CounterCubit>().add(),
        child: Text('Add'),
      ),
      ElevatedButton(
        key: const Key('counter_subtract'),
        onPressed: () => context.read<CounterCubit>().subtract(),
        child: Text('Subtract'),
      ),
    ],
  );
}
}
CounterTest
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

class MockCounterCubit extends MockCubit<CounterState> implements CounterCubit {
}

class FakeCounterState extends Fake implements CounterState {}

extension PumpApp on WidgetTester {
Future<void> pumpApp(Widget widget, CounterCubit cubit) async {
  registerFallbackValue<CounterState>(FakeCounterState());

  return pumpWidget(
    BlocProvider(
      create: (_) => cubit,
      child: MaterialApp(
        home: widget,
      ),
    ),
  );
}
}

void main() {
late CounterCubit cubit;

setUpAll(() {
  registerFallbackValue<CounterState>(FakeCounterState());
});

setUp(() {
  cubit = MockCounterCubit();
  when(() => cubit.state).thenReturn(CounterState());
});

group('group', () {
  testWidgets(
    'test',
    (tester) async {
      await tester.pumpApp(Counter(), cubit);
      await tester.tap(find.byKey(Key('counter_add')));

      verify(() => cubit.add()).called(1);
    },
  );
});
}

Expected behavior
With the verify in CounterTest, I was expecting that the test should go through but it didn't. It also reported that it found 2 matching calls instead of 1. I assume it matched both the add and subtract functions in the extension.

I tried moving the extension function add back to CounterCubit and it worked as expected. That's why I suspect there's something weird happening with extension functions.

Screenshots
N/A

Logs

Test logs
══║ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
The following TestFailure object was thrown running a test:
Expected: <1>
Actual: <2>
Unexpected number of calls

When the exception was thrown, this was the stack:
#0      fail (package:test_api/src/frontend/expect.dart:153:31)
#1      _expect (package:test_api/src/frontend/expect.dart:148:3)
#2      expect (package:test_api/src/frontend/expect.dart:57:3)
#3      VerificationResult.called (package:mocktail/src/mocktail.dart:591:5)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

The test description was:
test
════════════════════════════════════════════════════════════════════════════════════════════════════

Additional context
N/A

UntilCalled doesn't work for Future<T> Function

This is the example code to reproduce this issue:

main.dart

class Cat {
  final String name;
  final int lives;
  bool isSleep;

  Cat(this.name, {int lives = 9, bool isSleep = true})
      : lives = lives,
        isSleep = isSleep;

  Future<void> awake(String name) async {
    if (name.toLowerCase() == name) {
      await Future.delayed(Duration(seconds: 1));
      isSleep = false;
    }
    return;
  }

  Future<void> putToSleep() async {
    await Future.delayed(Duration(seconds: 2));
    isSleep = true;
  }
}

class CatManager {
  final cats = <Cat>[];

  void moreCats(Cat cat) {
    cats.add(cat);
  }

  void awakeCat(String name) {
    cats.forEach((cat) async {
      await cat.awake(name);
    });
  }
}

test.dart

class MockCat extends Mock implements Cat {}

void main() {
  group('Cat', () {
    late Cat cat;

    setUp(() {
      cat = MockCat();
    });

    test('Wake cats up', () async {
      final catManager = CatManager();
      catManager.cats.add(cat);
      catManager.awakeCat('miojo');

      await untilCalled(cat.awake(any()));
                ^^^^^^^^
               The argument type 'Future<void>' can't be assigned to the parameter type 'dynamic Function()'.dart(argument_type_not_assignable)
 
    });
  });
}

When I used mockito, even though I have an error at any, I didn't have the miss match error for untilCalled. It looks that they defined their getter as Future<Invocation> Function<T>(T) get untilCalled while yours is defined as Future<Invocation> Function<T>(T Function()) get untilCalled

Using a Delayed Future in Widget test stops the test from completing

@felangel thanks for this fantastic package, you've saved mocking for us in the NS age!

I've have run into an issue though and I'm not sure if this is a case of my not doing something correctly but I've found that trying to use Future.delayed() in a mocked method in a widgetTest causes the test to hang and never complete.

My code is:

testWidgets('home page shows progress indicator when loading',
      (tester) async {
    final api = MockApiService();
    final lvm = LoginViewModel(api);

    when(api).calls(#requestLoginCode).thenAnswer((_) async {
      await Future<void>.delayed(Duration(seconds: 1));
      return APIResult.Error;
    });

    final w = ProviderScope(
      overrides: [loginProvider.overrideWithProvider(Provider((ref) => lvm))],
      child: HomePage(),
    );

    await tester.pumpWidget(wrapForTest(w));

    await lvm.requestCode('12345');

    await tester.pump();

    await tester.pump(Duration(seconds: 2));

    final hasSpinner = tester.any(find.byType(CircularProgressIndicator));
  
    expect(hasSpinner, true);
  });

where requestCode() calls the ApiService objects requestLoginCode() method which returns a Future that completes once its network API call (in the real, nnon mocked ApiService class) completes.

If I remove the Future.delayed() everything works as expected, BUT I want the delay in there because I'm trying to test that a CircularProgressIndicator is displayed during the period before the Future returned by requestLoginCode completes.

I've included the Riverpod setup code, but I don't think its relevant here.

My suspicion is this is maybe due to widgetTests running inside a FakeAsync zone though according to this SO question this approach should work (or at least used to work with mockito).

Add support for type arguments matching

Is your feature request related to a problem? Please describe.

On a scenario where we have a mocked instance with methods that receive type arguments, proceedings like verify never take notice of type arguments,

// real calls
stub.someMethodWithTypeArgs<SomeType>()
stub.someMethodWithTypeArgs<SomeAnotherType>()

// verify call
final result = verify(() => stub.someMethodWithTypeArgs<SomeType>());

print(result.callCount); // will be 2, even though we are verifying inly for calls passing `SomeType`. 

Describe the solution you'd like
Take type arguments into consideration as much as positional and named arguments.

Describe alternatives you've considered
We could evolve to have Type Matchers like Any but probably not for the current state of the support of type arguments on dart.

verify(() => stub.someMethodWithTypeArgs<Any>());

Additional context
The fix would be probably be added here.

`When` throws exception when stubbing response

Here we have:

class Cat {
  Future<void> hunt(String place, String prey) async => prey;
}

then in test file:
[for demonstration, test doesn't do anything]

class MockCat extends Mock implements Cat {}

void main() {
late MockCat cat;
setUp(() {
    cat = MockCat();
  });

group('Cat behaviors', () {
    test('verify cat can hunt', () {
      when(() => cat.hunt('yard', 'mouse')).thenAnswer((invocation) async => 'mouse');
    });
  });
}

then it throws

_TypeError (type 'Null' is not a subtype of type 'Future<void>')

I have upgraded 'bloc_test' to 8.0.0 and while updating the tests because of syntax change, I face the above error.

What am I doing wrong?

Flutter doctor
[βœ“] Flutter (Channel beta, 2.0.1, on macOS 11.0.1 20B29 darwin-x64, locale en-US)
    β€’ Flutter version 2.0.1 at /Users/hashem/Desktop/Xcode_Projects/flutter
    β€’ Framework revision c5a4b4029c (2 weeks ago), 2021-03-04 09:47:48 -0800
    β€’ Engine revision 40441def69
    β€’ Dart version 2.12.0

[βœ“] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
    β€’ Android SDK at /Users/hashem/Library/Android/sdk
    β€’ Platform android-30, build-tools 30.0.2
    β€’ Java binary at: /Applications/Android
      Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    β€’ Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    β€’ All Android licenses accepted.

[βœ“] Xcode - develop for iOS and macOS
    β€’ Xcode at /Applications/Xcode.app/Contents/Developer
    β€’ Xcode 12.4, Build version 12D4e
    β€’ CocoaPods version 1.10.1

[βœ“] Chrome - develop for the web
    β€’ Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[βœ“] Android Studio (version 4.1)
    β€’ Android Studio at /Applications/Android Studio.app/Contents
    β€’ Flutter plugin can be installed from:
      πŸ”¨ https://plugins.jetbrains.com/plugin/9212-flutter
    β€’ Dart plugin can be installed from:
      πŸ”¨ https://plugins.jetbrains.com/plugin/6351-dart
    β€’ Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[βœ“] VS Code (version 1.54.3)
    β€’ VS Code at /Applications/Visual Studio Code.app/Contents
    β€’ Flutter extension version 3.20.0

[βœ“] Connected device (3 available)
    β€’ Hashem’s iPhone (mobile)   β€’ 00008030-00124C3021BA802E            β€’ ios            β€’
      iOS 14.4
    β€’ iPhone 12 Pro Max (mobile) β€’ 6E489C41-27D7-4EE7-A1A7-3383C53F1452 β€’ ios            β€’
      com.apple.CoreSimulator.SimRuntime.iOS-14-4 (simulator)
    β€’ Chrome (web)               β€’ chrome                               β€’ web-javascript β€’
      Google Chrome 89.0.4389.90

β€’ No issues found!

[QUESTION] Is it possible to stub a method in a real object?

abstract class ITest {
  Future<String?> get getSomething;

  Future<String?> get doSomething;
}

class MyTest implements ITest {
  static const staticField = 'staticField';

  @override
  Future<String?> get getSomething async {
    final somethingNullable = await ThirdPartyLib.getSomethingNullable();

    if (somethingNullable == null) return null;

    return somethingNullable.compareAginst(staticField)
        ? somethingNullSafe
        : null;
  }

  @override
  Future<String?> get getOtherThing async { // -------> I want to test this 
    final something = await getSomething; // ----------> Mocking this

    if (something == null) return null;

    final otherThing = processSomething(something);

    return otherThing;
  }
}

void main() {
  late final MyTest myTest;

  setUpAll(() {
    myTest = MyTest();
  });

  test('MUST result in null WHEN getSomething is null', () async {
    when(() => myTest.getSomething).thenAnswer((_) async => Future.value());

    final result = await myTest.getAnotherThing;

    expect(result, isNull);
  });
}

The code above fails with Bad state: No method stub was called from within `when()`. Was a real method called, or perhaps an extension method?

feat: improve `registerFallbackValue` error messaging

@felangel The example provided by @Amir-P above shows the issue. This sequence

...
group('description', () {
    MockOtherTestClass otherTestClass = MockOtherTestClass();
    when(() => otherTestClass.test(any())).thenReturn('expected');
    test('test', () {
...

will continue to tell you to add a setup that calls registerFallbackValue even after you add the recommended code.

The only way to make it work is to move the when into the test

...
group('description', () {
    MockOtherTestClass otherTestClass = MockOtherTestClass();
    test('test', () {
        when(() => otherTestClass.test(any())).thenReturn('expected');
...

Originally posted by @rich-j in #40 (comment)

Overhead on refactor of class

Is your feature request related to a problem? Please describe.
When I rename the method of a class that is mocked I have to manually update the tests.

Lets assume this simple class:

abstract class A {
   B add(B object);
}

In a test the class would be mocked:

class AMock extends Mock implements A {}

void main () {
   late AMock api;

   setUp((){
      api = AMock();
   });

   test('...', () {
      final b = B();
      when(api).calls(#add).thenReturn(b.copyWith(id: 'unique_id_from_api'));
      ... test continues ...
   });
}

First, there is no IntelliSense for #add which could introduce typos. Second, if add would be renamed I need to manually fix the #add in the tests.

Describe the solution you'd like
No real solution in my head right now. Just wanted to start a conversation on how to go forward with this as there is a need for an easy testing framework with null safety and besides this issue, I like the usability of mocktail!

Any thoughts on the topic?

Provide meaningful output for mock data

Describe the bug
When mock verification fails the output in the console is:

Instance of 'MocktailFailure'
_CallCountCall.times.      package:mocktail/src/mocktail.dart:251
main.<fn>.<fn>.<fn>.     test/plugin_controller_test.dart:74

To Reproduce
Create a test containing a mock and a test where the verify invocation fails.

e.g. verify(cat).calls('incorrect').times(1);

Expected behavior
Provide an exact message which verification of which object and method fails. Something like:

Expected cat.incorrect to be called 1 time(s) but actual call count was 0.

Additional context
I am using the latest version of Mocktail.

I think you either need to implement the toString method for the MocktailFailure class or just get rid of the wrapper.

registerFallbackValue seems to be not working as intended

I'm using registerFallbackValue at the top level of my main function to register a type instance. But tests are failing with logs telling me to use registerFallbackValue for the type I'm trying to any on it.

Bad state: A test tried to use `any` or `captureAny` on a parameter of type `TestModel`, but
registerFallbackValue was not previously called to register a fallback value for `TestModel`

To fix, do:

void main() {
  setUpAll(() {
    registerFallbackValue<TestModel>(TestModel());
  });
}

If you cannot easily create an instance of TestModel, consider defining a `Fake`:

class TestModelFake extends Fake implements TestModel {}

void main() {
  setUpAll(() {
    registerFallbackValue<TestModel>(TestModelFake());
  });
}

To Reproduce

class TestClass {
  final OtherTestClass testClass;

  TestClass(this.testClass);

  test() {
    testClass.test(TestModel());
  }
}

class OtherTestClass {
  test(TestModel model) {}
}

class TestModel {}
class MockOtherTestClass extends Mock implements OtherTestClass {}

void main() {
  setUp(() {
    registerFallbackValue<TestModel>(TestModel());
  });

  group('description', () {
    MockOtherTestClass otherTestClass = MockOtherTestClass();
    when(() => otherTestClass.test(any())).thenReturn('expected');
    test('test', () {
      verify(() => otherTestClass.test(TestModel())).called(1);
    });
  });
}

Logs

[βœ“] Flutter (Channel stable, 2.0.2, on macOS 11.2.3 20D91 darwin-arm, locale en)
    β€’ Flutter version 2.0.2 at /Users/amirpanahandeh/flutter
    β€’ Framework revision 8962f6dc68 (3 weeks ago), 2021-03-11 13:22:20 -0800
    β€’ Engine revision 5d8bf811b3
    β€’ Dart version 2.12.1

[βœ“] Android toolchain - develop for Android devices (Android SDK version 30.0.2)
    β€’ Android SDK at /Users/amirpanahandeh/SDK
    β€’ Platform android-30, build-tools 30.0.2
    β€’ ANDROID_HOME = /Users/amirpanahandeh/SDK
    β€’ Java binary at: /Applications/Android
      Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    β€’ Java version OpenJDK Runtime Environment (build
      1.8.0_242-release-1644-b3-6915495)
    β€’ All Android licenses accepted.

[βœ“] Xcode - develop for iOS and macOS
    β€’ Xcode at /Applications/Xcode.app/Contents/Developer
    β€’ Xcode 12.4, Build version 12D4e
    β€’ CocoaPods version 1.10.1

[βœ“] Chrome - develop for the web
    β€’ Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[βœ“] Android Studio (version 4.1)
    β€’ Android Studio at /Applications/Android Studio.app/Contents
    β€’ Flutter plugin can be installed from:
      πŸ”¨ https://plugins.jetbrains.com/plugin/9212-flutter
    β€’ Dart plugin can be installed from:
      πŸ”¨ https://plugins.jetbrains.com/plugin/6351-dart
    β€’ Java version OpenJDK Runtime Environment (build
      1.8.0_242-release-1644-b3-6915495)

[βœ“] VS Code (version 1.54.3)
    β€’ VS Code at /Applications/Visual Studio Code.app/Contents
    β€’ Flutter extension version 3.20.0

[βœ“] Connected device (1 available)
    β€’ Chrome (web) β€’ chrome β€’ web-javascript β€’ Google Chrome 89.0.4389.114

β€’ No issues found!

Question: type 'Null' is not a subtype of type 'Future<Response> on http post test

hello guys, if there is someone there who can help me i would appreciate it too much. I'm testing a post type request, my goal is to ensure that the class will receive the correct url, but I'm getting this error:

type 'Null' is not a subtype of type 'Future'

My code:

import 'package:faker/faker.dart';
import 'package:http/http.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

class HttpAdapter {
  final Client client;

  HttpAdapter({required this.client});

  Future<void> request({
    required Uri url,
  }) async {}
}

class ClientSpy extends Mock implements Client {}

void main() {
  group("post", () {
    test("deve chamar o metodo post com valores corretos", () async {
      final client = ClientSpy();
      final sut = HttpAdapter(client: client);
      final url = faker.internet.httpUrl();
      final Uri uri = Uri.parse(url);

      await sut.request(url: uri);
      client.post(uri);

      verify(() => client.post(uri)).called(1);
    });
  });
}

How to mock Future<void>function({Rect? bounds}) ?

Describe the bug
I am trying to verify that a method on a viewModel gets called on button tap but I am getting an error.

The original method looks like this :

Future<void> exportNotes({Rect? bounds}) {}

I have tried to mock it like this :
when(() => _vmMock.exportNotes()).thenAnswer((_) => Future.value());

and then run the following test :

testWidgets('calls exportNotes if button is pressed', (tester) async {
          await _pumpAssessmentDetailsCard(tester);
          await tester.pumpAndSettle();

          final exportButtonTextFinder = find.text('Export Notes');
          final exportButtonFinder = find.ancestor(
              of: exportButtonTextFinder,
              matching:
                  find.byWidgetPredicate((widget) => widget is OutlinedButton));

          expect(exportButtonFinder, findsOneWidget);

          await tester.tap(exportButtonFinder);
          verify(() => _vmMock.exportNotes()).called(1);

          tester.idle();
        });

So the button exists but when the tester runs .tap() I am getting an error

══║ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK β•žβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
The following _TypeError was thrown running a test:
type 'Null' is not a subtype of type 'Future<void>'

Expected behavior
I was expecting to get a verification of the executed method

**Logs **
No issues found! (ran in 3.2s)

[βœ“] Flutter (Channel stable, 2.2.3, on macOS 11.4 20F71 darwin-x64, locale de-DE)
[βœ“] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[βœ“] Xcode - develop for iOS and macOS
[βœ“] Chrome - develop for the web
[βœ“] Android Studio (version 4.2)
[βœ“] Connected device (1 available)

β€’ No issues found!

type 'Null' is not a subtype of type 'Future<Either<FailureInterface, bool>>'

Test:

class RemoveEmployeeMock extends Mock implements RemoveEmployeeInterface {}

void main() {
  late final RemoveEmployeeInterface useCase;
  setUpAll(() {
    useCase = RemoveEmployeeMock();
  });
test('Should return TRUE when remove employee', () async {
  when(() => useCase.call(id: 'id'))
      .thenAnswer((_) async => const Right(true));

var result = await useCase.call(id: '');
expect(result, const Right(true));
  });
}

RemoveEmployeeInterface:

abstract class RemoveEmployeeInterface {
  Future<Either<FailureInterface, bool>> call({required String id});
}

class RemoveEmployee implements RemoveEmployeeInterface {
  final EmployeeRepositoryInterface repository;
  RemoveEmployee({required this.repository});

  @override
  Future<Either<FailureInterface, bool>> call({required String id}) {
    return repository.removeEmployee(id: id);
  }
}

Partial mock

I'm not sure that it's possible in Dart, but it would be nice to have partial mock.

What it means:
You have a mocked class with methods.
Any method works as in a real class instance until you apply 'when' to the method.

Use `any` or `captureAny` on a parameter - error when Any with parameter

Hi all,

thanks a lot for your work on mocktail - very useful one!
I very hope you could help me with the below issue:

when(() => _mockClient.get(any(that: startsWith('${_dataConfig.apiEndPoint}')), headers: requestHeaders))
          .thenAnswer((_) async => http.Response('{ "accounts": [] }', 200));

With Sound Null Safety On - I'm getting the following error here:

Bad state: A test tried to use any or captureAny on a parameter of type Uri, but
registerFallbackValue was not previously called to register a fallback value for Uri.
...
etc

I know how to fix similar error(s) in case if there is simple any() object.
But as you see in my case I have additional parameter to that "any".

Could you please give me any ideas how I could translate such code into Null Safety version and keep that "startsWith" in place?

Or any ideas how it can be implemented without "any" but still be checked for "startsWith" condition?

Appreciate your help on this!

Thanks!

-Egor

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.