@@ -11,6 +11,8 @@ _WhenCall _whenCall = null;
1111final List <_VerifyCall > _verifyCalls = < _VerifyCall > [];
1212final _TimeStampProvider _timer = new _TimeStampProvider ();
1313final List _capturedArgs = [];
14+ final List <_ArgMatcher > _typedArgs = < _ArgMatcher > [];
15+ final Map <String , _ArgMatcher > _typedNamedArgs = < String , _ArgMatcher > {};
1416
1517class Mock {
1618 final List <RealCall > _realCalls = < RealCall > [];
@@ -24,6 +26,9 @@ class Mock {
2426 }
2527
2628 dynamic noSuchMethod (Invocation invocation) {
29+ if (_typedArgs.isNotEmpty || _typedNamedArgs.isNotEmpty) {
30+ invocation = _reconstituteInvocation (invocation);
31+ }
2732 if (_whenInProgress) {
2833 _whenCall = new _WhenCall (this , invocation);
2934 return null ;
@@ -48,6 +53,132 @@ class Mock {
4853 String toString () => _givenName != null ? _givenName : runtimeType.toString ();
4954}
5055
56+ // Return a new [Invocation], reconstituted from [invocation], [_typedArgs],
57+ // and [_typedNamedArgs].
58+ Invocation _reconstituteInvocation (Invocation invocation) {
59+ var newInvocation = new FakeInvocation (invocation);
60+ return newInvocation;
61+ }
62+
63+ /// An Invocation implementation that allows all attributes to be passed into
64+ /// the constructor.
65+ class FakeInvocation 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 FakeInvocation (Invocation invocation) {
74+ if (_typedArgs.isEmpty && _typedNamedArgs.isEmpty) {
75+ throw new StateError ("FakeInvocation called when no typed calls have been saved." );
76+ }
77+
78+ // Handle named arguments first, so that we can provide useful errors for
79+ // the various bad states. If all is well with the named arguments, then we
80+ // can process the positional arguments, and resort to more general errors
81+ // if the state is still bad.
82+ var namedArguments = _reconstituteNamedArgs (invocation);
83+ var positionalArguments = _reconstitutePositionalArgs (invocation);
84+
85+ _typedArgs.clear ();
86+ _typedNamedArgs.clear ();
87+
88+ return new FakeInvocation ._(
89+ invocation.memberName,
90+ positionalArguments,
91+ namedArguments,
92+ invocation.isGetter,
93+ invocation.isMethod,
94+ invocation.isSetter);
95+ }
96+
97+ static Map <Symbol ,dynamic > _reconstituteNamedArgs (Invocation invocation) {
98+ var namedArguments = < Symbol , dynamic > {};
99+ var _typedNamedArgSymbols = _typedNamedArgs.keys.map ((name) => new Symbol (name));
100+ invocation.namedArguments.forEach ((name, arg) {
101+ if (arg == null ) {
102+ if (! _typedNamedArgSymbols.contains (name)) {
103+ // Incorrect usage of [typed], something like:
104+ // `when(obj.fn(a: typed(any)))`.
105+ throw new ArgumentError (
106+ 'A typed argument was passed in as a named argument named "$name ", '
107+ 'but did not a value for its name. Each typed argument that is '
108+ 'passed as a named argument needs to specify the `name` argument. '
109+ 'For example: `when(obj.fn(x: typed(any, name: "x")))`.' );
110+ }
111+ } else {
112+ // Add each real named argument that was _not_ passed with [typed].
113+ namedArguments[name] = arg;
114+ }
115+ });
116+
117+ _typedNamedArgs.forEach ((name, arg) {
118+ Symbol nameSymbol = new Symbol (name);
119+ if (! invocation.namedArguments.containsKey (nameSymbol)) {
120+ // Incorrect usage of [name], something like:
121+ // `when(obj.fn(typed(any, name: 'a')))`.
122+ throw new ArgumentError (
123+ 'A typed argument was declared with name $name , but was not passed '
124+ 'as an argument named $name .' );
125+ }
126+ if (invocation.namedArguments[nameSymbol] != null ) {
127+ // Incorrect usage of [name], something like:
128+ // `when(obj.fn(a: typed(any, name: 'b'), b: "string"))`.
129+ throw new ArgumentError (
130+ 'A typed argument was declared with name $name , but a different '
131+ 'value (${invocation .namedArguments [nameSymbol ]}) was passed as '
132+ '$name .' );
133+ }
134+ namedArguments[nameSymbol] = arg;
135+ });
136+
137+ return namedArguments;
138+ }
139+
140+ static List <dynamic > _reconstitutePositionalArgs (Invocation invocation) {
141+ var positionalArguments = < dynamic > [];
142+ var nullPositionalArguments =
143+ invocation.positionalArguments.where ((arg) => arg == null );
144+ if (_typedArgs.length != nullPositionalArguments.length) {
145+ throw new ArgumentError (
146+ 'null arguments are not allowed alongside typed(); use '
147+ '"typed(eq(null))"' );
148+ }
149+ int i = 0 ;
150+ int j = 0 ;
151+ while (i < _typedArgs.length && j < invocation.positionalArguments.length) {
152+ var arg = _typedArgs[i];
153+ if (invocation.positionalArguments[j] == null ) {
154+ // [typed] was used; add the [_ArgMatcher] given to [typed].
155+ positionalArguments.add (arg);
156+ i++ ;
157+ j++ ;
158+ } else {
159+ // [typed] was not used; add the [_ArgMatcher] from [invocation].
160+ positionalArguments.add (invocation.positionalArguments[j]);
161+ j++ ;
162+ }
163+ }
164+ while (j < invocation.positionalArguments.length) {
165+ // Some trailing non-[typed] arguments.
166+ positionalArguments.add (invocation.positionalArguments[j]);
167+ j++ ;
168+ }
169+
170+ return positionalArguments;
171+ }
172+
173+ FakeInvocation ._(
174+ this .memberName,
175+ this .positionalArguments,
176+ this .namedArguments,
177+ this .isGetter,
178+ this .isMethod,
179+ this .isSetter);
180+ }
181+
51182named (var mock, {String name, int hashCode}) => mock
52183 .._givenName = name
53184 .._givenHashCode = hashCode;
@@ -291,6 +422,15 @@ get captureAny => new _ArgMatcher(anything, true);
291422captureThat (Matcher matcher) => new _ArgMatcher (matcher, true );
292423argThat (Matcher matcher) => new _ArgMatcher (matcher, false );
293424
425+ /*=T*/ typed/*<T>*/ (_ArgMatcher matcher, {String name}) {
426+ if (name == null ) {
427+ _typedArgs.add (matcher);
428+ } else {
429+ _typedNamedArgs[name] = matcher;
430+ }
431+ return null ;
432+ }
433+
294434class VerificationResult {
295435 List captured = [];
296436 int callCount;
@@ -404,3 +544,14 @@ void logInvocations(List<Mock> mocks) {
404544 print (inv.toString ());
405545 });
406546}
547+
548+ /// Should only be used during Mockito testing.
549+ void resetMockitoState () {
550+ _whenInProgress = false ;
551+ _verificationInProgress = false ;
552+ _whenCall = null ;
553+ _verifyCalls.clear ();
554+ _capturedArgs.clear ();
555+ _typedArgs.clear ();
556+ _typedNamedArgs.clear ();
557+ }
0 commit comments