@@ -10,6 +10,8 @@ _WhenCall _whenCall = null;
1010final List <_VerifyCall > _verifyCalls = < _VerifyCall > [];
1111final _TimeStampProvider _timer = new _TimeStampProvider ();
1212final List _capturedArgs = [];
13+ final List <_ArgMatcher > _typedArgs = < _ArgMatcher > [];
14+ final Map <String , _ArgMatcher > _typedNamedArgs = < String , _ArgMatcher > {};
1315
1416// Hidden from the public API, used by spy.dart.
1517void setDefaultResponse (Mock mock, dynamic defaultResponse) {
@@ -31,6 +33,9 @@ class Mock {
3133 }
3234
3335 dynamic noSuchMethod (Invocation invocation) {
36+ if (_typedArgs.isNotEmpty || _typedNamedArgs.isNotEmpty) {
37+ invocation = new _InvocationForTypedArguments (invocation);
38+ }
3439 if (_whenInProgress) {
3540 _whenCall = new _WhenCall (this , invocation);
3641 return null ;
@@ -55,6 +60,137 @@ class Mock {
5560 String toString () => _givenName != null ? _givenName : runtimeType.toString ();
5661}
5762
63+ /// An Invocation implementation that takes arguments from [_typedArgs] and
64+ /// [_typedNamedArgs] .
65+ class _InvocationForTypedArguments extends Invocation {
66+ final Symbol memberName;
67+ final Map <Symbol , dynamic > namedArguments;
68+ final List <dynamic > positionalArguments;
69+ final bool isGetter;
70+ final bool isMethod;
71+ final bool isSetter;
72+
73+ factory _InvocationForTypedArguments (Invocation invocation) {
74+ if (_typedArgs.isEmpty && _typedNamedArgs.isEmpty) {
75+ throw new StateError (
76+ "_InvocationForTypedArguments called when no typed calls have been saved." );
77+ }
78+
79+ // Handle named arguments first, so that we can provide useful errors for
80+ // the various bad states. If all is well with the named arguments, then we
81+ // can process the positional arguments, and resort to more general errors
82+ // if the state is still bad.
83+ var namedArguments = _reconstituteNamedArgs (invocation);
84+ var positionalArguments = _reconstitutePositionalArgs (invocation);
85+
86+ _typedArgs.clear ();
87+ _typedNamedArgs.clear ();
88+
89+ return new _InvocationForTypedArguments ._(
90+ invocation.memberName,
91+ positionalArguments,
92+ namedArguments,
93+ invocation.isGetter,
94+ invocation.isMethod,
95+ invocation.isSetter);
96+ }
97+
98+ // Reconstitutes the named arguments in an invocation from [_typedNamedArgs].
99+ //
100+ // The namedArguments in [invocation] which are null should be represented
101+ // by a stored value in [_typedNamedArgs]. The null presumably came from
102+ // [typed].
103+ static Map <Symbol ,dynamic > _reconstituteNamedArgs (Invocation invocation) {
104+ var namedArguments = < Symbol , dynamic > {};
105+ var _typedNamedArgSymbols = _typedNamedArgs.keys.map ((name) => new Symbol (name));
106+
107+ // Iterate through [invocation]'s named args, validate them, and add them
108+ // to the return map.
109+ invocation.namedArguments.forEach ((name, arg) {
110+ if (arg == null ) {
111+ if (! _typedNamedArgSymbols.contains (name)) {
112+ // Incorrect usage of [typed], something like:
113+ // `when(obj.fn(a: typed(any)))`.
114+ throw new ArgumentError (
115+ 'A typed argument was passed in as a named argument named "$name ", '
116+ 'but did not pass a value for `named`. Each typed argument that is '
117+ 'passed as a named argument needs to specify the `named` argument. '
118+ 'For example: `when(obj.fn(x: typed(any, named: "x")))`.' );
119+ }
120+ } else {
121+ // Add each real named argument that was _not_ passed with [typed].
122+ namedArguments[name] = arg;
123+ }
124+ });
125+
126+ // Iterate through the stored named args (stored with [typed]), validate
127+ // them, and add them to the return map.
128+ _typedNamedArgs.forEach ((name, arg) {
129+ Symbol nameSymbol = new Symbol (name);
130+ if (! invocation.namedArguments.containsKey (nameSymbol)) {
131+ throw new ArgumentError (
132+ 'A typed argument was declared as named $name , but was not passed '
133+ 'as an argument named $name .\n\n '
134+ 'BAD: when(obj.fn(typed(any, named: "a")))\n '
135+ 'GOOD: when(obj.fn(a: typed(any, named: "a")))' );
136+ }
137+ if (invocation.namedArguments[nameSymbol] != null ) {
138+ throw new ArgumentError (
139+ 'A typed argument was declared as named $name , but a different '
140+ 'value (${invocation .namedArguments [nameSymbol ]}) was passed as '
141+ '$name .\n\n '
142+ 'BAD: when(obj.fn(b: typed(any, name: "a")))\n '
143+ 'GOOD: when(obj.fn(b: typed(any, name: "b")))' );
144+ }
145+ namedArguments[nameSymbol] = arg;
146+ });
147+
148+ return namedArguments;
149+ }
150+
151+ static List <dynamic > _reconstitutePositionalArgs (Invocation invocation) {
152+ var positionalArguments = < dynamic > [];
153+ var nullPositionalArguments =
154+ invocation.positionalArguments.where ((arg) => arg == null );
155+ if (_typedArgs.length != nullPositionalArguments.length) {
156+ throw new ArgumentError (
157+ 'null arguments are not allowed alongside typed(); use '
158+ '"typed(eq(null))"' );
159+ }
160+ int typedIndex = 0 ;
161+ int positionalIndex = 0 ;
162+ while (typedIndex < _typedArgs.length &&
163+ positionalIndex < invocation.positionalArguments.length) {
164+ var arg = _typedArgs[typedIndex];
165+ if (invocation.positionalArguments[positionalIndex] == null ) {
166+ // [typed] was used; add the [_ArgMatcher] given to [typed].
167+ positionalArguments.add (arg);
168+ typedIndex++ ;
169+ positionalIndex++ ;
170+ } else {
171+ // [typed] was not used; add the [_ArgMatcher] from [invocation].
172+ positionalArguments.add (invocation.positionalArguments[positionalIndex]);
173+ positionalIndex++ ;
174+ }
175+ }
176+ while (positionalIndex < invocation.positionalArguments.length) {
177+ // Some trailing non-[typed] arguments.
178+ positionalArguments.add (invocation.positionalArguments[positionalIndex]);
179+ positionalIndex++ ;
180+ }
181+
182+ return positionalArguments;
183+ }
184+
185+ _InvocationForTypedArguments ._(
186+ this .memberName,
187+ this .positionalArguments,
188+ this .namedArguments,
189+ this .isGetter,
190+ this .isMethod,
191+ this .isSetter);
192+ }
193+
58194named (var mock, {String name, int hashCode}) => mock
59195 .._givenName = name
60196 .._givenHashCode = hashCode;
@@ -300,6 +436,15 @@ get captureAny => new _ArgMatcher(anything, true);
300436captureThat (Matcher matcher) => new _ArgMatcher (matcher, true );
301437argThat (Matcher matcher) => new _ArgMatcher (matcher, false );
302438
439+ /*=T*/ typed/*<T>*/ (_ArgMatcher matcher, {String named}) {
440+ if (named == null ) {
441+ _typedArgs.add (matcher);
442+ } else {
443+ _typedNamedArgs[named] = matcher;
444+ }
445+ return null ;
446+ }
447+
303448class VerificationResult {
304449 List captured = [];
305450 int callCount;
@@ -413,3 +558,14 @@ void logInvocations(List<Mock> mocks) {
413558 print (inv.toString ());
414559 });
415560}
561+
562+ /// Should only be used during Mockito testing.
563+ void resetMockitoState () {
564+ _whenInProgress = false ;
565+ _verificationInProgress = false ;
566+ _whenCall = null ;
567+ _verifyCalls.clear ();
568+ _capturedArgs.clear ();
569+ _typedArgs.clear ();
570+ _typedNamedArgs.clear ();
571+ }
0 commit comments