@@ -10,6 +10,8 @@ _WhenCall _whenCall = null;
10
10
final List <_VerifyCall > _verifyCalls = < _VerifyCall > [];
11
11
final _TimeStampProvider _timer = new _TimeStampProvider ();
12
12
final List _capturedArgs = [];
13
+ final List <_ArgMatcher > _typedArgs = < _ArgMatcher > [];
14
+ final Map <String , _ArgMatcher > _typedNamedArgs = < String , _ArgMatcher > {};
13
15
14
16
// Hidden from the public API, used by spy.dart.
15
17
void setDefaultResponse (Mock mock, dynamic defaultResponse) {
@@ -31,6 +33,9 @@ class Mock {
31
33
}
32
34
33
35
dynamic noSuchMethod (Invocation invocation) {
36
+ if (_typedArgs.isNotEmpty || _typedNamedArgs.isNotEmpty) {
37
+ invocation = new _InvocationForTypedArguments (invocation);
38
+ }
34
39
if (_whenInProgress) {
35
40
_whenCall = new _WhenCall (this , invocation);
36
41
return null ;
@@ -55,6 +60,137 @@ class Mock {
55
60
String toString () => _givenName != null ? _givenName : runtimeType.toString ();
56
61
}
57
62
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
+
58
194
named (var mock, {String name, int hashCode}) => mock
59
195
.._givenName = name
60
196
.._givenHashCode = hashCode;
@@ -300,6 +436,15 @@ get captureAny => new _ArgMatcher(anything, true);
300
436
captureThat (Matcher matcher) => new _ArgMatcher (matcher, true );
301
437
argThat (Matcher matcher) => new _ArgMatcher (matcher, false );
302
438
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
+
303
448
class VerificationResult {
304
449
List captured = [];
305
450
int callCount;
@@ -413,3 +558,14 @@ void logInvocations(List<Mock> mocks) {
413
558
print (inv.toString ());
414
559
});
415
560
}
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