-
Notifications
You must be signed in to change notification settings - Fork 160
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
Handle new noSuchMethod mechanics #122
Comments
Concrete example: class Cat {
bool eatFood(String food, {bool hungry}) => true;
}
// Mock class
class MockCat extends Mock implements Cat {}
// mock creation
var cat = new MockCat(); Then stubbing with: when(cat.eatFood(typed(any))).thenReturn(true); Mockito yells, "Hey! you apparently wrote an argument matcher for |
I don't have any great ideas. I have some... OK ideas. No change to mockitoWe just require users to spell out in every stub, for every named argument, Provide a little helper for optional args you don't care about:when(obj.fn(typed(any), typed(any), foo: typed(any, named: "foo")),
andAnythingFor: ["bar", "baz"])... I think this is the same herculean effort... Resort to surprising results when argument matchers aren't named.when(obj.fn(typed(any), typed(any), foo: typed(argThat(contains("item 1")))))... Note the missing name for the Since the Invocation would be guaranteed to basically match the shape of the parameters (as opposed to the shape of the arguments today), we could check for some errors, I think. "This only contains 2 positional args, but you passed 3. Did you forget a name?" But then optional and named would be very confusing to users. Is that survivable? I think there are more consequences to error states that I haven't thought of here... Others? 😄 |
I think that 3rd option is probably what we need to do. It will definitely mean a poor user experience for casual Mockito users (as opposed to all of us hardcore Mockito users); Mockito cannot help very well when users don't name their named arguments. Can we write a linter plugin maybe? |
I think its worth waiting on the resolution of dart-lang/sdk#33031; it's not clear me to the new nSM semantics fix any problem (as you already mentioned), and given that the feature is used infrequently anyway (basically Mockito), I think we could relax the changes... |
Personally I vote that if the dart language team wants to make this change (which I doubt will happen in dart2,) they need to work with us to have a way to support mocking, and they need to do most of the heavy lifting. For example can we get more signals from nSM or the Invocation? If not who is signing up for a code gen solution? As evidence by the code we have in google3 mocking is vital to the testing ecosystem and it needs first class support. |
Trying to take a look at the situation, I noted that there are 'no issues' from What does it take to get something like the "Hey! you apparently wrote an argument matcher for hungry, because I got a null there. Why didn't you name your hungry argument matcher?" message mentioned above (given that using the Mockito branch
Note that this change was accepted by the language team quite a while ago: The relevant feature specification was clarified on Oct 5 2017 to specify this treatment of optional parameters, and it had its status changed from 'under discussion' to 'under implementation' on Dec 1 2017 in this SDK commit. That said, we must of course make things work and minimize the disruption. I hope we can spot a good solution soon. |
Dang, I thought that was a valid repro. I haven't repro'd myself. Let me check out the SDK, sync to dart-lang/sdk@7d5025e, and try to repro. |
Sorry, the mock method call needs at least one arg matcher, so I've changed that line above to: when(cat.eatFood(typed(any))).thenReturn(false); Then running Unhandled exception:
Invalid argument(s): A typed argument was passed in as a named argument named "Symbol("hungry")", but did not pass a value for `named`. Each typed argument that is passed as a named argument needs to specify the `named` argument. For example: `when(obj.fn(x: typed(any, named: "x")))`.
#0 _InvocationForTypedArguments._reconstituteNamedArgs.<anonymous closure> (package:mockito/src/mock.dart:208:11)
#1 __InternalLinkedHashMap&_HashVMBase&MapMixin&_LinkedHashMapMixin.forEach (dart:collection/runtime/libcompact_hash.dart:358:8)
#2 MapView.forEach (dart:collection/maps.dart:340:10)
#3 _InvocationForTypedArguments._reconstituteNamedArgs (package:mockito/src/mock.dart:203:31)
#4 new _InvocationForTypedArguments (package:mockito/src/mock.dart:176:26)
#5 _useTypedInvocationIfSet (package:mockito/src/mock.dart:145:22)
#6 Mock.noSuchMethod (package:mockito/src/mock.dart:102:18)
#7 MockCat.eatFood (file:///Users/srawlins/Downloads/mockito_122/bin/main.dart:5:8)
#8 main (file:///Users/srawlins/Downloads/mockito_122/bin/main.dart:16:12)
#9 _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:279:19)
#10 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12) |
Thanks! I'll take another look later today. |
As far as I understand, you can already pass valid parameter values instead of registering a matcher, so you can do: when(mock.foo(x: 42)).thenReturn(42); Is this correct? As I understand it, you use a If you always get the full argument list for both positional and named arguments, then you know the signature of the function immediately. If there are any named arguments, you can detect a missing named matcher simply by there being more unnamed matchers than So if someone does: when(obj.fn(typed(any), typed(any), foo: typed(argThat(contains("item 1"))))).. then you register three unnamed matchers, but the call to In fact, this should work even now - if you get more unnamed matchers than (This would stop working if we get both optional positional and named parameters on the same function, then you can't know if an optional positional parameter with default value |
Yes.
Oh, I forgot that we don't have that yet. I think you're right, we should be able to generate errors. The remaining issue is a breaking change in the user expectations of the presence of arguments. Let's look at another example: class A {
int fn(one, [two: true]) => 0
}
class MockA extends Mock implements A {}
main() {
var a = new MockA();
when(a.fn(typed(any))).thenReturn(7);
when(a.fn(typed(any), true)).thenReturn(8);
a.fn(1); // Dart 1: returns 7. Dart 2 w/ NSM forwarding: returns 8.
} (Same for named args.) This situation is probably not common... we'll have to look internally to see what kind of a breaking change this would be. |
With the new example I could reproduce the issue, and it seems that some adjustments in/around The issue with the expectations about arguments being not present or present (with the value which is also the default) should not be useful for a mock object, because the corresponding non-mock object would not be able to make that distinction anyway (except if it would somehow magically be able to use a So it shouldn't be so bad after all, right? |
We'll have to see. |
With full parameter forwarding, those two calls would have the same matchers (both would pass However, in this example you have behavior that cannot be implemented in plain Dart, so it's probably not the expected behavior of the actual class. |
I'm not advocating that the old behavior is better. It's just different.
We'll have to see. |
In Dart 2, it seems likely that NSM will change:
positionalArguments
andnamedArguments
).See dart-lang/sdk#33031.
Mockito then sees all these null values in the Invocation, and assumes that the user forgot to use
foo: typed(any, named: "foo")
. It requires each named argument to be given a name, so that Mockito can store them properly. How to handle this proposed change?CC @matanlurey @TedSander
The text was updated successfully, but these errors were encountered: