Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mockito and compatibility with generic methods #155

Open
matanlurey opened this issue Jul 11, 2018 · 10 comments
Open

Mockito and compatibility with generic methods #155

matanlurey opened this issue Jul 11, 2018 · 10 comments

Comments

@matanlurey
Copy link
Contributor

Related issue was #152.

In Dart2, it is impossible to implement generic methods (correctly) with Mockito.

Specifically:

  1. It isn't possible to match on type arguments, which are part of the signature:
abstract class Fetcher {
  T fetchOfType<T>();
}

class MockFetcher extends Mock implements Fetcher {}

void main() {
  var fetcher = new MockFetcher();

  // ERROR: Invalid syntax, `any` is not a type variable.
  when(fetcher.fetchOfType<any>()).thenAnswer((_) => /* ... */);    
}
  1. It isn't possible to return reified generics (without using mirrors):
 abstract class Cat { /* ... */ }
 abstract class Request<T> {
   T get responseForTesting;
 }


 abstract class RpcHandler {
   Future<T> handle<T>(Request<T> request);    
 }

 class MockRpcHandler extends Mock implements RpcHandler {}

 void main(){
   var rpcHandler = new MockRpcHandler();
   when(rpcHandler.handle(any)).thenAnswer((i) {
     final response = (i.positionalArguments.first as Request).responseForTesting;
     return new Future.value(response);
   });

   // ERROR: Future<dynamic> is not type of Future<Cat>.
   var cat = rpcHandler.handle(new TestRequest<Cat>());
 }

Unfortunately today we give no helpful hints or documentation. I think we should change this, and in default, in Mockito 4, make it impossible to mock a method with one or more generic type arguments (at least by default). We could start with making this opt-in:

void main() {
  Mock.throwOnGenericStub = true;
}

This was just hit (critically) internally, and hidden by DDC whitelisting :(

@matanlurey
Copy link
Contributor Author

One extra itch I hit internally, the latter example can be example if someone writes:

when(rpcHandler.any(any)).thenAnswer((_) => new Future.value(new Cat());

... so we might not want to ban this, perhaps this should just be documented cleanly.

@matanlurey matanlurey changed the title Make Mockito incompatible with generic methods Mockito and compatibility with generic methods Jul 12, 2018
@enyo
Copy link
Contributor

enyo commented Aug 6, 2018

Is this related to this issue?

import 'package:mockito/mockito.dart';

typedef MyHandler<T>(T arg1);

defaultHandler<T>(T arg1) {}

class Foo {
  bar<T>({MyHandler<T> arg: defaultHandler}) {}
}

class MockMandrillClient extends Mock implements Foo {}

main() {}

When trying to run this "test", I get this exception:

Failed to load "/Volumes/CaseFS/mandrill-api/test/foo_test.dart":
Unable to spawn isolate: Type parameter T is not indexed
#0      TypeParameterIndexer.[] (package:kernel/binary/ast_to_binary.dart:2073)
#1      BinaryPrinter.visitTypeParameterType (package:kernel/binary/ast_to_binary.dart:1703)
#2      TypeParameterType.accept (package:kernel/ast.dart:5035)
#3      BinaryPrinter.writeNode (package:kernel/binary/ast_to_binary.dart:246)
#4      LimitedBinaryPrinter.writeNode (package:kernel/binary/limited_ast_to_binary.dart:55)
#5      BinaryPrinter.writeNodeList (package:kernel/binary/ast_to_binary.dart:238)
#6      BinaryPrinter.visitInstantiation (package:kernel/binary/ast_to_binary.dart:1332)
#7      Instantiation.accept (package:kernel/ast.dart:2959)
[...]

Without mockito, this code works fine.

@srawlins
Copy link
Member

srawlins commented Aug 7, 2018

@enyo That is a head scratcher; can you do a dart --version? Are you running pub run test or dart my_test.dart? What version of mockito (should be in pubspec.lock)?

@enyo
Copy link
Contributor

enyo commented Aug 7, 2018

@srawlins I'm running dart 2.0.0:

/usr/local/Cellar/dart/2.0.0/libexec/bin/dart

mockito v3.0.0

It's quite interesting, because when you remove implements Foo from the class definition it also doesn't fail.

@srawlins
Copy link
Member

For better or worse, @enyo, this is a Dart SDK bug. dart-lang/sdk#34122

@enyo
Copy link
Contributor

enyo commented Aug 13, 2018

I thought that was very likely. Thanks for looking into it!

@blackmenthor
Copy link

Hi, how are you guys doing with this issue?

I actually ran into the same problem, I'm using this https://pub.dev/packages/injector and apparently mock classes is having problems with generic types.
For instance, if I ran

injector.getDependency<A>

and

injector.getDependency<B>

it would return the same object (the first object type alphabetically) because mock's inability to match generic type arguments.

@srawlins
Copy link
Member

We haven't looked into this issue in a long time. It just doesn't seem to crop up. An injector does sound like a case where it might be warranted.

One tricky catch to fixing this issue: it will be a breaking change. Where currently a method call would be matched, it may no longer, due to unequal type arguments.

@natebosch
Copy link
Member

Potentially off topic tip: A dependency injector is something you really don't want to mock. There shouldn't be any behavior that is tied to something expensive or unstable, and it should be safe to set up a real injector within a test, and shouldn't have the type of side effects to warrant a verify that certain methods were called. https://github.com/dart-lang/mockito#best-practices

@felangel
Copy link

felangel commented Mar 1, 2022

This is supported in v0.3.0 of mocktail in case that helps anyone.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants