Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(158)

Side by Side Diff: pkg/unittest/lib/mock.dart

Issue 210413002: pkg/unittest: using matcher and mock from packages (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: releasing v0.10.1 Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/unittest/lib/mirror_matchers.dart ('k') | pkg/unittest/lib/src/core_matchers.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /// `unittest.mock` has been moved to the `matcher` package.
6 * A simple mocking/spy library. 6 ///
7 * 7 /// Add `matcher` to your `pubspec.yaml` file and import it via
8 * ## Installing ## 8 /// `import 'package:matcher/mirror_matchers.dart';`
9 * 9 @deprecated
10 * Use [pub][] to install this package. Add the following to your `pubspec.yaml`
11 * file.
12 *
13 * dependencies:
14 * unittest: any
15 *
16 * Then run `pub install`.
17 *
18 * Import this into your Dart code with:
19 *
20 * import 'package:unittest/mock.dart';
21 *
22 * For more information, see the [unittest package on pub.dartlang.org]
23 * (http://pub.dartlang.org/packages/unittest).
24 *
25 * ## Using ##
26 *
27 * To create a mock objects for some class T, create a new class using:
28 *
29 * class MockT extends Mock implements T {};
30 *
31 * Then specify the [Behavior] of the Mock for different methods using
32 * [when] (to select the method and parameters) and then the [Action]s
33 * for the [Behavior] by calling [thenReturn], [alwaysReturn], [thenThrow],
34 * [alwaysThrow], [thenCall] or [alwaysCall].
35 *
36 * [thenReturn], [thenThrow] and [thenCall] are one-shot so you would
37 * typically call these more than once to specify a sequence of actions;
38 * this can be done with chained calls, e.g.:
39 *
40 * m.when(callsTo('foo')).
41 * thenReturn(0).thenReturn(1).thenReturn(2);
42 *
43 * [thenCall] and [alwaysCall] allow you to proxy mocked methods, chaining
44 * to some other implementation. This provides a way to implement 'spies'.
45 *
46 * For getters and setters, use "get foo" and "set foo"-style arguments
47 * to [callsTo].
48 *
49 * You can disable logging for a particular [Behavior] easily:
50 *
51 * m.when(callsTo('bar')).logging = false;
52 *
53 * You can then use the mock object. Once you are done, to verify the
54 * behavior, use [getLogs] to extract a relevant subset of method call
55 * logs and apply [Matchers] to these through calling [verify].
56 *
57 * A Mock can be given a name when constructed. In this case instead of
58 * keeping its own log, it uses a shared log. This can be useful to get an
59 * audit trail of interleaved behavior. It is the responsibility of the user
60 * to ensure that mock names, if used, are unique.
61 *
62 * Limitations:
63 *
64 * * only positional parameters are supported (up to 10);
65 * * to mock getters you will need to include parentheses in the call
66 * (e.g. m.length() will work but not m.length).
67 *
68 * Here is a simple example:
69 *
70 * class MockList extends Mock implements List {};
71 *
72 * List m = new MockList();
73 * m.when(callsTo('add', anything)).alwaysReturn(0);
74 *
75 * m.add('foo');
76 * m.add('bar');
77 *
78 * getLogs(m, callsTo('add', anything)).verify(happenedExactly(2));
79 * getLogs(m, callsTo('add', 'foo')).verify(happenedOnce);
80 * getLogs(m, callsTo('add', 'isNull)).verify(neverHappened);
81 *
82 * Note that we don't need to provide argument matchers for all arguments,
83 * but we do need to provide arguments for all matchers. So this is allowed:
84 *
85 * m.when(callsTo('add')).alwaysReturn(0);
86 * m.add(1, 2);
87 *
88 * But this is not allowed and will throw an exception:
89 *
90 * m.when(callsTo('add', anything, anything)).alwaysReturn(0);
91 * m.add(1);
92 *
93 * Here is a way to implement a 'spy', which is where we log the call
94 * but then hand it off to some other function, which is the same
95 * method in a real instance of the class being mocked:
96 *
97 * class Foo {
98 * bar(a, b, c) => a + b + c;
99 * }
100 *
101 * class MockFoo extends Mock implements Foo {
102 * Foo real;
103 * MockFoo() {
104 * real = new Foo();
105 * this.when(callsTo('bar')).alwaysCall(real.bar);
106 * }
107 * }
108 *
109 * However, there is an even easier way, by calling [Mock.spy], e.g.:
110 *
111 * var foo = new Foo();
112 * var spy = new Mock.spy(foo);
113 * print(spy.bar(1, 2, 3));
114 *
115 * Spys created with Mock.spy do not have user-defined behavior;
116 * they are simply proxies, and thus will throw an exception if
117 * you call [when]. They capture all calls in the log, so you can
118 * do assertions on their history, such as:
119 *
120 * spy.getLogs(callsTo('bar')).verify(happenedOnce);
121 *
122 * [pub]: http://pub.dartlang.org
123 */
124
125 library unittest.mock; 10 library unittest.mock;
126 11
127 import 'dart:mirrors'; 12 export 'package:mock/mock.dart';
128 import 'dart:collection' show LinkedHashMap;
129
130 import 'matcher.dart';
131
132 /**
133 * The error formatter for mocking is a bit different from the default one
134 * for unit testing; instead of the third argument being a 'reason'
135 * it is instead a [signature] describing the method signature filter
136 * that was used to select the logs that were verified.
137 */
138 String _mockingErrorFormatter(actual, Matcher matcher, String signature,
139 Map matchState, bool verbose) {
140 var description = new StringDescription();
141 description.add('Expected ${signature} ').addDescriptionOf(matcher).
142 add('\n but: ');
143 matcher.describeMismatch(actual, description, matchState, verbose).add('.');
144 return description.toString();
145 }
146
147 /**
148 * The failure handler for the [expect()] calls that occur in [verify()]
149 * methods in the mock objects. This calls the real failure handler used
150 * by the unit test library after formatting the error message with
151 * the custom formatter.
152 */
153 class _MockFailureHandler implements FailureHandler {
154 FailureHandler proxy;
155 _MockFailureHandler(this.proxy);
156 void fail(String reason) {
157 proxy.fail(reason);
158 }
159 void failMatch(actual, Matcher matcher, String reason,
160 Map matchState, bool verbose) {
161 proxy.fail(_mockingErrorFormatter(actual, matcher, reason,
162 matchState, verbose));
163 }
164 }
165
166 _MockFailureHandler _mockFailureHandler = null;
167
168 /** Sentinel value for representing no argument. */
169 class _Sentinel {
170 const _Sentinel();
171 }
172 const _noArg = const _Sentinel();
173
174 /** The ways in which a call to a mock method can be handled. */
175 class Action {
176 /** Do nothing (void method) */
177 static const IGNORE = const Action._('IGNORE');
178
179 /** Return a supplied value. */
180 static const RETURN = const Action._('RETURN');
181
182 /** Throw a supplied value. */
183 static const THROW = const Action._('THROW');
184
185 /** Call a supplied function. */
186 static const PROXY = const Action._('PROXY');
187
188 const Action._(this.name);
189
190 final String name;
191
192 String toString() => 'Action: $name';
193 }
194
195 /**
196 * The behavior of a method call in the mock library is specified
197 * with [Responder]s. A [Responder] has a [value] to throw
198 * or return (depending on the type of [action]),
199 * and can either be one-shot, multi-shot, or infinitely repeating,
200 * depending on the value of [count (1, greater than 1, or 0 respectively).
201 */
202 class Responder {
203 final Object value;
204 final Action action;
205 int count;
206 Responder(this.value, [this.count = 1, this.action = Action.RETURN]);
207 }
208
209 /**
210 * A [CallMatcher] is a special matcher used to match method calls (i.e.
211 * a method name and set of arguments). It is not a [Matcher] like the
212 * unit test [Matcher], but instead represents a method name and a
213 * collection of [Matcher]s, one per argument, that will be applied
214 * to the parameters to decide if the method call is a match.
215 */
216 class CallMatcher {
217 Matcher nameFilter;
218 List<Matcher> argMatchers;
219
220 /**
221 * Constructor for [CallMatcher]. [name] can be null to
222 * match anything, or a literal [String], a predicate [Function],
223 * or a [Matcher]. The various arguments can be scalar values or
224 * [Matcher]s.
225 */
226 CallMatcher([name,
227 arg0 = _noArg,
228 arg1 = _noArg,
229 arg2 = _noArg,
230 arg3 = _noArg,
231 arg4 = _noArg,
232 arg5 = _noArg,
233 arg6 = _noArg,
234 arg7 = _noArg,
235 arg8 = _noArg,
236 arg9 = _noArg]) {
237 if (name == null) {
238 nameFilter = anything;
239 } else {
240 nameFilter = wrapMatcher(name);
241 }
242 argMatchers = new List<Matcher>();
243 if (identical(arg0, _noArg)) return;
244 argMatchers.add(wrapMatcher(arg0));
245 if (identical(arg1, _noArg)) return;
246 argMatchers.add(wrapMatcher(arg1));
247 if (identical(arg2, _noArg)) return;
248 argMatchers.add(wrapMatcher(arg2));
249 if (identical(arg3, _noArg)) return;
250 argMatchers.add(wrapMatcher(arg3));
251 if (identical(arg4, _noArg)) return;
252 argMatchers.add(wrapMatcher(arg4));
253 if (identical(arg5, _noArg)) return;
254 argMatchers.add(wrapMatcher(arg5));
255 if (identical(arg6, _noArg)) return;
256 argMatchers.add(wrapMatcher(arg6));
257 if (identical(arg7, _noArg)) return;
258 argMatchers.add(wrapMatcher(arg7));
259 if (identical(arg8, _noArg)) return;
260 argMatchers.add(wrapMatcher(arg8));
261 if (identical(arg9, _noArg)) return;
262 argMatchers.add(wrapMatcher(arg9));
263 }
264
265 /**
266 * We keep our behavior specifications in a Map, which is keyed
267 * by the [CallMatcher]. To make the keys unique and to get a
268 * descriptive value for the [CallMatcher] we have this override
269 * of [toString()].
270 */
271 String toString() {
272 Description d = new StringDescription();
273 d.addDescriptionOf(nameFilter);
274 // If the nameFilter was a simple string - i.e. just a method name -
275 // strip the quotes to make this more natural in appearance.
276 if (d.toString()[0] == "'") {
277 d.replace(d.toString().substring(1, d.toString().length - 1));
278 }
279 d.add('(');
280 for (var i = 0; i < argMatchers.length; i++) {
281 if (i > 0) d.add(', ');
282 d.addDescriptionOf(argMatchers[i]);
283 }
284 d.add(')');
285 return d.toString();
286 }
287
288 /**
289 * Given a [method] name and list of [arguments], return true
290 * if it matches this [CallMatcher.
291 */
292 bool matches(String method, List arguments) {
293 var matchState = {};
294 if (!nameFilter.matches(method, matchState)) {
295 return false;
296 }
297 var numArgs = (arguments == null) ? 0 : arguments.length;
298 if (numArgs < argMatchers.length) {
299 throw new Exception("Less arguments than matchers for $method.");
300 }
301 for (var i = 0; i < argMatchers.length; i++) {
302 if (!argMatchers[i].matches(arguments[i], matchState)) {
303 return false;
304 }
305 }
306 return true;
307 }
308 }
309
310 /**
311 * Returns a [CallMatcher] for the specified signature. [method] can be
312 * null to match anything, or a literal [String], a predicate [Function],
313 * or a [Matcher]. The various arguments can be scalar values or [Matcher]s.
314 * To match getters and setters, use "get " and "set " prefixes on the names.
315 * For example, for a property "foo", you could use "get foo" and "set foo"
316 * as literal string arguments to callsTo to match the getter and setter
317 * of "foo".
318 */
319 CallMatcher callsTo([method,
320 arg0 = _noArg,
321 arg1 = _noArg,
322 arg2 = _noArg,
323 arg3 = _noArg,
324 arg4 = _noArg,
325 arg5 = _noArg,
326 arg6 = _noArg,
327 arg7 = _noArg,
328 arg8 = _noArg,
329 arg9 = _noArg]) {
330 return new CallMatcher(method, arg0, arg1, arg2, arg3, arg4,
331 arg5, arg6, arg7, arg8, arg9);
332 }
333
334 /**
335 * A [Behavior] represents how a [Mock] will respond to one particular
336 * type of method call.
337 */
338 class Behavior {
339 CallMatcher matcher; // The method call matcher.
340 List<Responder> actions; // The values to return/throw or proxies to call.
341 bool logging = true;
342
343 Behavior (this.matcher) {
344 actions = new List<Responder>();
345 }
346
347 /**
348 * Adds a [Responder] that returns a [value] for [count] calls
349 * (1 by default).
350 */
351 Behavior thenReturn(value, [count = 1]) {
352 actions.add(new Responder(value, count, Action.RETURN));
353 return this; // For chaining calls.
354 }
355
356 /** Adds a [Responder] that repeatedly returns a [value]. */
357 Behavior alwaysReturn(value) {
358 return thenReturn(value, 0);
359 }
360
361 /**
362 * Adds a [Responder] that throws [value] [count]
363 * times (1 by default).
364 */
365 Behavior thenThrow(value, [count = 1]) {
366 actions.add(new Responder(value, count, Action.THROW));
367 return this; // For chaining calls.
368 }
369
370 /** Adds a [Responder] that throws [value] endlessly. */
371 Behavior alwaysThrow(value) {
372 return thenThrow(value, 0);
373 }
374
375 /**
376 * [thenCall] creates a proxy Responder, that is called [count]
377 * times (1 by default; 0 is used for unlimited calls, and is
378 * exposed as [alwaysCall]). [value] is the function that will
379 * be called with the same arguments that were passed to the
380 * mock. Proxies can be used to wrap real objects or to define
381 * more complex return/throw behavior. You could even (if you
382 * wanted) use proxies to emulate the behavior of thenReturn;
383 * e.g.:
384 *
385 * m.when(callsTo('foo')).thenReturn(0)
386 *
387 * is equivalent to:
388 *
389 * m.when(callsTo('foo')).thenCall(() => 0)
390 */
391 Behavior thenCall(value, [count = 1]) {
392 actions.add(new Responder(value, count, Action.PROXY));
393 return this; // For chaining calls.
394 }
395
396 /** Creates a repeating proxy call. */
397 Behavior alwaysCall(value) {
398 return thenCall(value, 0);
399 }
400
401 /** Returns true if a method call matches the [Behavior]. */
402 bool matches(String method, List args) => matcher.matches(method, args);
403
404 /** Returns the [matcher]'s representation. */
405 String toString() => matcher.toString();
406 }
407
408 /**
409 * Every call to a [Mock] object method is logged. The logs are
410 * kept in instances of [LogEntry].
411 */
412 class LogEntry {
413 /** The time of the event. */
414 DateTime time;
415
416 /** The mock object name, if any. */
417 final String mockName;
418
419 /** The method name. */
420 final String methodName;
421
422 /** The parameters. */
423 final List args;
424
425 /** The behavior that resulted. */
426 final Action action;
427
428 /** The value that was returned (if no throw). */
429 final value;
430
431 LogEntry(this.mockName, this.methodName,
432 this.args, this.action, [this.value]) {
433 time = new DateTime.now();
434 }
435
436 String _pad2(int val) => (val >= 10 ? '$val' : '0$val');
437
438 String toString([DateTime baseTime]) {
439 Description d = new StringDescription();
440 if (baseTime == null) {
441 // Show absolute time.
442 d.add('${time.hour}:${_pad2(time.minute)}:'
443 '${_pad2(time.second)}.${time.millisecond}> ');
444 } else {
445 // Show relative time.
446 int delta = time.millisecondsSinceEpoch - baseTime.millisecondsSinceEpoch;
447 int secs = delta ~/ 1000;
448 int msecs = delta % 1000;
449 d.add('$secs.$msecs> ');
450 }
451 d.add('${_qualifiedName(mockName, methodName)}(');
452 if (args != null) {
453 for (var i = 0; i < args.length; i++) {
454 if (i != 0) d.add(', ');
455 d.addDescriptionOf(args[i]);
456 }
457 }
458 d.add(') ${action == Action.THROW ? "threw" : "returned"} ');
459 d.addDescriptionOf(value);
460 return d.toString();
461 }
462 }
463
464 /** Utility function for optionally qualified method names */
465 String _qualifiedName(owner, String method) {
466 if (owner == null || identical(owner, anything)) {
467 return method;
468 } else if (owner is Matcher) {
469 Description d = new StringDescription();
470 d.addDescriptionOf(owner);
471 d.add('.');
472 d.add(method);
473 return d.toString();
474 } else {
475 return '$owner.$method';
476 }
477 }
478
479 /**
480 * [StepValidator]s are used by [stepwiseValidate] in [LogEntryList], which
481 * iterates through the list and call the [StepValidator] function with the
482 * log [List] and position. The [StepValidator] should return the number of
483 * positions to advance upon success, or zero upon failure. When zero is
484 * returned an error is reported.
485 */
486 typedef int StepValidator(List<LogEntry> logs, int pos);
487
488 /**
489 * We do verification on a list of [LogEntry]s. To allow chaining
490 * of calls to verify, we encapsulate such a list in the [LogEntryList]
491 * class.
492 */
493 class LogEntryList {
494 String filter;
495 List<LogEntry> logs;
496 LogEntryList([this.filter]) {
497 logs = new List<LogEntry>();
498 }
499
500 /** Add a [LogEntry] to the log. */
501 add(LogEntry entry) => logs.add(entry);
502
503 /** Get the first entry, or null if no entries. */
504 get first => (logs == null || logs.length == 0) ? null : logs[0];
505
506 /** Get the last entry, or null if no entries. */
507 get last => (logs == null || logs.length == 0) ? null : logs.last;
508
509 /** Creates a LogEntry predicate function from the argument. */
510 Function _makePredicate(arg) {
511 if (arg == null) {
512 return (e) => true;
513 } else if (arg is CallMatcher) {
514 return (e) => arg.matches(e.methodName, e.args);
515 } else if (arg is Function) {
516 return arg;
517 } else {
518 throw new Exception("Invalid argument to _makePredicate.");
519 }
520 }
521
522 /**
523 * Create a new [LogEntryList] consisting of [LogEntry]s from
524 * this list that match the specified [mockNameFilter] and [logFilter].
525 * [mockNameFilter] can be null, a [String], a predicate [Function],
526 * or a [Matcher]. If [mockNameFilter] is null, this is the same as
527 * [anything].
528 * If [logFilter] is null, all entries in the log will be returned.
529 * Otherwise [logFilter] should be a [CallMatcher] or predicate function
530 * that takes a [LogEntry] and returns a bool.
531 * If [destructive] is true, the log entries are removed from the
532 * original list.
533 */
534 LogEntryList getMatches([mockNameFilter,
535 logFilter,
536 Matcher actionMatcher,
537 bool destructive = false]) {
538 if (mockNameFilter == null) {
539 mockNameFilter = anything;
540 } else {
541 mockNameFilter = wrapMatcher(mockNameFilter);
542 }
543 Function entryFilter = _makePredicate(logFilter);
544 String filterName = _qualifiedName(mockNameFilter, logFilter.toString());
545 LogEntryList rtn = new LogEntryList(filterName);
546 var matchState = {};
547 for (var i = 0; i < logs.length; i++) {
548 LogEntry entry = logs[i];
549 if (mockNameFilter.matches(entry.mockName, matchState) &&
550 entryFilter(entry)) {
551 if (actionMatcher == null ||
552 actionMatcher.matches(entry, matchState)) {
553 rtn.add(entry);
554 if (destructive) {
555 int startIndex = i--;
556 logs.removeRange(startIndex, startIndex + 1);
557 }
558 }
559 }
560 }
561 return rtn;
562 }
563
564 /** Apply a unit test [Matcher] to the [LogEntryList]. */
565 LogEntryList verify(Matcher matcher) {
566 if (_mockFailureHandler == null) {
567 _mockFailureHandler =
568 new _MockFailureHandler(getOrCreateExpectFailureHandler());
569 }
570 expect(logs, matcher, reason:filter, failureHandler: _mockFailureHandler);
571 return this;
572 }
573
574 /**
575 * Iterate through the list and call the [validator] function with the
576 * log [List] and position. The [validator] should return the number of
577 * positions to advance upon success, or zero upon failure. When zero is
578 * returned an error is reported. [reason] can be used to provide a
579 * more descriptive failure message. If a failure occurred false will be
580 * returned (unless the failure handler itself threw an exception);
581 * otherwise true is returned.
582 * The use case here is to perform more complex validations; for example
583 * we may want to assert that the return value from some function is
584 * later used as a parameter to a following function. If we filter the logs
585 * to include just these two functions we can write a simple validator to
586 * do this check.
587 */
588 bool stepwiseValidate(StepValidator validator, [String reason = '']) {
589 if (_mockFailureHandler == null) {
590 _mockFailureHandler =
591 new _MockFailureHandler(getOrCreateExpectFailureHandler());
592 }
593 var i = 0;
594 while (i < logs.length) {
595 var n = validator(logs, i);
596 if (n == 0) {
597 if (reason.length > 0) {
598 reason = ': $reason';
599 }
600 _mockFailureHandler.fail("Stepwise validation failed at $filter "
601 "position $i$reason");
602 return false;
603 } else {
604 i += n;
605 }
606 }
607 return true;
608 }
609
610 /**
611 * Turn the logs into human-readable text. If [baseTime] is specified
612 * then each entry is prefixed with the offset from that time in
613 * milliseconds; otherwise the time of day is used.
614 */
615 String toString([DateTime baseTime]) {
616 String s = '';
617 for (var e in logs) {
618 s = '$s${e.toString(baseTime)}\n';
619 }
620 return s;
621 }
622
623 /**
624 * Find the first log entry that satisfies [logFilter] and
625 * return its position. A search [start] position can be provided
626 * to allow for repeated searches. [logFilter] can be a [CallMatcher],
627 * or a predicate function that takes a [LogEntry] argument and returns
628 * a bool. If [logFilter] is null, it will match any [LogEntry].
629 * If no entry is found, then [failureReturnValue] is returned.
630 * After each check the position is updated by [skip], so using
631 * [skip] of -1 allows backward searches, using a [skip] of 2 can
632 * be used to check pairs of adjacent entries, and so on.
633 */
634 int findLogEntry(logFilter, [int start = 0, int failureReturnValue = -1,
635 skip = 1]) {
636 logFilter = _makePredicate(logFilter);
637 int pos = start;
638 while (pos >= 0 && pos < logs.length) {
639 if (logFilter(logs[pos])) {
640 return pos;
641 }
642 pos += skip;
643 }
644 return failureReturnValue;
645 }
646
647 /**
648 * Returns log events that happened up to the first one that
649 * satisfies [logFilter]. If [inPlace] is true, then returns
650 * this LogEntryList after removing the from the first satisfier;
651 * onwards otherwise a new list is created. [description]
652 * is used to create a new name for the resulting list.
653 * [defaultPosition] is used as the index of the matching item in
654 * the case that no match is found.
655 */
656 LogEntryList _head(logFilter, bool inPlace,
657 String description, int defaultPosition) {
658 if (filter != null) {
659 description = '$filter $description';
660 }
661 int pos = findLogEntry(logFilter, 0, defaultPosition);
662 if (inPlace) {
663 if (pos < logs.length) {
664 logs.removeRange(pos, logs.length);
665 }
666 filter = description;
667 return this;
668 } else {
669 LogEntryList newList = new LogEntryList(description);
670 for (var i = 0; i < pos; i++) {
671 newList.logs.add(logs[i]);
672 }
673 return newList;
674 }
675 }
676
677 /**
678 * Returns log events that happened from the first one that
679 * satisfies [logFilter]. If [inPlace] is true, then returns
680 * this LogEntryList after removing the entries up to the first
681 * satisfier; otherwise a new list is created. [description]
682 * is used to create a new name for the resulting list.
683 * [defaultPosition] is used as the index of the matching item in
684 * the case that no match is found.
685 */
686 LogEntryList _tail(logFilter, bool inPlace,
687 String description, int defaultPosition) {
688 if (filter != null) {
689 description = '$filter $description';
690 }
691 int pos = findLogEntry(logFilter, 0, defaultPosition);
692 if (inPlace) {
693 if (pos > 0) {
694 logs.removeRange(0, pos);
695 }
696 filter = description;
697 return this;
698 } else {
699 LogEntryList newList = new LogEntryList(description);
700 while (pos < logs.length) {
701 newList.logs.add(logs[pos++]);
702 }
703 return newList;
704 }
705 }
706
707 /**
708 * Returns log events that happened after [when]. If [inPlace]
709 * is true, then it returns this LogEntryList after removing
710 * the entries that happened up to [when]; otherwise a new
711 * list is created.
712 */
713 LogEntryList after(DateTime when, [bool inPlace = false]) =>
714 _tail((e) => e.time.isAfter(when), inPlace, 'after $when', logs.length);
715
716 /**
717 * Returns log events that happened from [when] onwards. If
718 * [inPlace] is true, then it returns this LogEntryList after
719 * removing the entries that happened before [when]; otherwise
720 * a new list is created.
721 */
722 LogEntryList from(DateTime when, [bool inPlace = false]) =>
723 _tail((e) => !e.time.isBefore(when), inPlace, 'from $when', logs.length);
724
725 /**
726 * Returns log events that happened until [when]. If [inPlace]
727 * is true, then it returns this LogEntryList after removing
728 * the entries that happened after [when]; otherwise a new
729 * list is created.
730 */
731 LogEntryList until(DateTime when, [bool inPlace = false]) =>
732 _head((e) => e.time.isAfter(when), inPlace, 'until $when', logs.length);
733
734 /**
735 * Returns log events that happened before [when]. If [inPlace]
736 * is true, then it returns this LogEntryList after removing
737 * the entries that happened from [when] onwards; otherwise a new
738 * list is created.
739 */
740 LogEntryList before(DateTime when, [bool inPlace = false]) =>
741 _head((e) => !e.time.isBefore(when),
742 inPlace,
743 'before $when',
744 logs.length);
745
746 /**
747 * Returns log events that happened after [logEntry]'s time.
748 * If [inPlace] is true, then it returns this LogEntryList after
749 * removing the entries that happened up to [when]; otherwise a new
750 * list is created. If [logEntry] is null the current time is used.
751 */
752 LogEntryList afterEntry(LogEntry logEntry, [bool inPlace = false]) =>
753 after(logEntry == null ? new DateTime.now() : logEntry.time);
754
755 /**
756 * Returns log events that happened from [logEntry]'s time onwards.
757 * If [inPlace] is true, then it returns this LogEntryList after
758 * removing the entries that happened before [when]; otherwise
759 * a new list is created. If [logEntry] is null the current time is used.
760 */
761 LogEntryList fromEntry(LogEntry logEntry, [bool inPlace = false]) =>
762 from(logEntry == null ? new DateTime.now() : logEntry.time);
763
764 /**
765 * Returns log events that happened until [logEntry]'s time. If
766 * [inPlace] is true, then it returns this LogEntryList after removing
767 * the entries that happened after [when]; otherwise a new
768 * list is created. If [logEntry] is null the epoch time is used.
769 */
770 LogEntryList untilEntry(LogEntry logEntry, [bool inPlace = false]) =>
771 until(logEntry == null ?
772 new DateTime.fromMillisecondsSinceEpoch(0) : logEntry.time);
773
774 /**
775 * Returns log events that happened before [logEntry]'s time. If
776 * [inPlace] is true, then it returns this LogEntryList after removing
777 * the entries that happened from [when] onwards; otherwise a new
778 * list is created. If [logEntry] is null the epoch time is used.
779 */
780 LogEntryList beforeEntry(LogEntry logEntry, [bool inPlace = false]) =>
781 before(logEntry == null ?
782 new DateTime.fromMillisecondsSinceEpoch(0) : logEntry.time);
783
784 /**
785 * Returns log events that happened after the first event in [segment].
786 * If [inPlace] is true, then it returns this LogEntryList after removing
787 * the entries that happened earlier; otherwise a new list is created.
788 */
789 LogEntryList afterFirst(LogEntryList segment, [bool inPlace = false]) =>
790 afterEntry(segment.first, inPlace);
791
792 /**
793 * Returns log events that happened after the last event in [segment].
794 * If [inPlace] is true, then it returns this LogEntryList after removing
795 * the entries that happened earlier; otherwise a new list is created.
796 */
797 LogEntryList afterLast(LogEntryList segment, [bool inPlace = false]) =>
798 afterEntry(segment.last, inPlace);
799
800 /**
801 * Returns log events that happened from the time of the first event in
802 * [segment] onwards. If [inPlace] is true, then it returns this
803 * LogEntryList after removing the earlier entries; otherwise a new list
804 * is created.
805 */
806 LogEntryList fromFirst(LogEntryList segment, [bool inPlace = false]) =>
807 fromEntry(segment.first, inPlace);
808
809 /**
810 * Returns log events that happened from the time of the last event in
811 * [segment] onwards. If [inPlace] is true, then it returns this
812 * LogEntryList after removing the earlier entries; otherwise a new list
813 * is created.
814 */
815 LogEntryList fromLast(LogEntryList segment, [bool inPlace = false]) =>
816 fromEntry(segment.last, inPlace);
817
818 /**
819 * Returns log events that happened until the first event in [segment].
820 * If [inPlace] is true, then it returns this LogEntryList after removing
821 * the entries that happened later; otherwise a new list is created.
822 */
823 LogEntryList untilFirst(LogEntryList segment, [bool inPlace = false]) =>
824 untilEntry(segment.first, inPlace);
825
826 /**
827 * Returns log events that happened until the last event in [segment].
828 * If [inPlace] is true, then it returns this LogEntryList after removing
829 * the entries that happened later; otherwise a new list is created.
830 */
831 LogEntryList untilLast(LogEntryList segment, [bool inPlace = false]) =>
832 untilEntry(segment.last, inPlace);
833
834 /**
835 * Returns log events that happened before the first event in [segment].
836 * If [inPlace] is true, then it returns this LogEntryList after removing
837 * the entries that happened later; otherwise a new list is created.
838 */
839 LogEntryList beforeFirst(LogEntryList segment, [bool inPlace = false]) =>
840 beforeEntry(segment.first, inPlace);
841
842 /**
843 * Returns log events that happened before the last event in [segment].
844 * If [inPlace] is true, then it returns this LogEntryList after removing
845 * the entries that happened later; otherwise a new list is created.
846 */
847 LogEntryList beforeLast(LogEntryList segment, [bool inPlace = false]) =>
848 beforeEntry(segment.last, inPlace);
849
850 /**
851 * Iterate through the LogEntryList looking for matches to the entries
852 * in [keys]; for each match found the closest [distance] neighboring log
853 * entries that match [mockNameFilter] and [logFilter] will be included in
854 * the result. If [isPreceding] is true we use the neighbors that precede
855 * the matched entry; else we use the neighbors that followed.
856 * If [includeKeys] is true then the entries in [keys] that resulted in
857 * entries in the output list are themselves included in the output list. If
858 * [distance] is zero then all matches are included.
859 */
860 LogEntryList _neighboring(bool isPreceding,
861 LogEntryList keys,
862 mockNameFilter,
863 logFilter,
864 int distance,
865 bool includeKeys) {
866 String filterName = 'Calls to '
867 '${_qualifiedName(mockNameFilter, logFilter.toString())} '
868 '${isPreceding?"preceding":"following"} ${keys.filter}';
869
870 LogEntryList rtn = new LogEntryList(filterName);
871
872 // Deal with the trivial case.
873 if (logs.length == 0 || keys.logs.length == 0) {
874 return rtn;
875 }
876
877 // Normalize the mockNameFilter and logFilter values.
878 if (mockNameFilter == null) {
879 mockNameFilter = anything;
880 } else {
881 mockNameFilter = wrapMatcher(mockNameFilter);
882 }
883 logFilter = _makePredicate(logFilter);
884
885 // The scratch list is used to hold matching entries when we
886 // are doing preceding neighbors. The remainingCount is used to
887 // keep track of how many matching entries we can still add in the
888 // current segment (0 if we are doing doing following neighbors, until
889 // we get our first key match).
890 List scratch = null;
891 int remainingCount = 0;
892 if (isPreceding) {
893 scratch = new List();
894 remainingCount = logs.length;
895 }
896
897 var keyIterator = keys.logs.iterator;
898 keyIterator.moveNext();
899 LogEntry keyEntry = keyIterator.current;
900 Map matchState = {};
901
902 for (LogEntry logEntry in logs) {
903 // If we have a log entry match, copy the saved matches from the
904 // scratch buffer into the return list, as well as the matching entry,
905 // if appropriate, and reset the scratch buffer. Continue processing
906 // from the next key entry.
907 if (keyEntry == logEntry) {
908 if (scratch != null) {
909 int numToCopy = scratch.length;
910 if (distance > 0 && distance < numToCopy) {
911 numToCopy = distance;
912 }
913 for (var i = scratch.length - numToCopy; i < scratch.length; i++) {
914 rtn.logs.add(scratch[i]);
915 }
916 scratch.clear();
917 } else {
918 remainingCount = distance > 0 ? distance : logs.length;
919 }
920 if (includeKeys) {
921 rtn.logs.add(keyEntry);
922 }
923 if (keyIterator.moveNext()) {
924 keyEntry = keyIterator.current;
925 } else if (isPreceding) { // We're done.
926 break;
927 }
928 } else if (remainingCount > 0 &&
929 mockNameFilter.matches(logEntry.mockName, matchState) &&
930 logFilter(logEntry)) {
931 if (scratch != null) {
932 scratch.add(logEntry);
933 } else {
934 rtn.logs.add(logEntry);
935 --remainingCount;
936 }
937 }
938 }
939 return rtn;
940 }
941
942 /**
943 * Iterate through the LogEntryList looking for matches to the entries
944 * in [keys]; for each match found the closest [distance] prior log entries
945 * that match [mocknameFilter] and [logFilter] will be included in the result.
946 * If [includeKeys] is true then the entries in [keys] that resulted in
947 * entries in the output list are themselves included in the output list. If
948 * [distance] is zero then all matches are included.
949 *
950 * The idea here is that you could find log entries that are related to
951 * other logs entries in some temporal sense. For example, say we have a
952 * method commit() that returns -1 on failure. Before commit() gets called
953 * the value being committed is created by process(). We may want to find
954 * the calls to process() that preceded calls to commit() that failed.
955 * We could do this with:
956 *
957 * print(log.preceding(log.getLogs(callsTo('commit'), returning(-1)),
958 * logFilter: callsTo('process')).toString());
959 *
960 * We might want to include the details of the failing calls to commit()
961 * to see what parameters were passed in, in which case we would set
962 * [includeKeys].
963 *
964 * As another simple example, say we wanted to know the three method
965 * calls that immediately preceded each failing call to commit():
966 *
967 * print(log.preceding(log.getLogs(callsTo('commit'), returning(-1)),
968 * distance: 3).toString());
969 */
970 LogEntryList preceding(LogEntryList keys,
971 {mockNameFilter: null,
972 logFilter: null,
973 int distance: 1,
974 bool includeKeys: false}) =>
975 _neighboring(true, keys, mockNameFilter, logFilter,
976 distance, includeKeys);
977
978 /**
979 * Iterate through the LogEntryList looking for matches to the entries
980 * in [keys]; for each match found the closest [distance] subsequent log
981 * entries that match [mocknameFilter] and [logFilter] will be included in
982 * the result. If [includeKeys] is true then the entries in [keys] that
983 * resulted in entries in the output list are themselves included in the
984 * output list. If [distance] is zero then all matches are included.
985 * See [preceding] for a usage example.
986 */
987 LogEntryList following(LogEntryList keys,
988 {mockNameFilter: null,
989 logFilter: null,
990 int distance: 1,
991 bool includeKeys: false}) =>
992 _neighboring(false, keys, mockNameFilter, logFilter,
993 distance, includeKeys);
994 }
995
996 /**
997 * [_TimesMatcher]s are used to make assertions about the number of
998 * times a method was called.
999 */
1000 class _TimesMatcher extends Matcher {
1001 final int min, max;
1002
1003 const _TimesMatcher(this.min, [this.max = -1]);
1004
1005 bool matches(logList, Map matchState) => logList.length >= min &&
1006 (max < 0 || logList.length <= max);
1007
1008 Description describe(Description description) {
1009 description.add('to be called ');
1010 if (max < 0) {
1011 description.add('at least $min');
1012 } else if (max == min) {
1013 description.add('$max');
1014 } else if (min == 0) {
1015 description.add('at most $max');
1016 } else {
1017 description.add('between $min and $max');
1018 }
1019 return description.add(' times');
1020 }
1021
1022 Description describeMismatch(logList, Description mismatchDescription,
1023 Map matchState, bool verbose) =>
1024 mismatchDescription.add('was called ${logList.length} times');
1025 }
1026
1027 /** [happenedExactly] matches an exact number of calls. */
1028 Matcher happenedExactly(count) {
1029 return new _TimesMatcher(count, count);
1030 }
1031
1032 /** [happenedAtLeast] matches a minimum number of calls. */
1033 Matcher happenedAtLeast(count) {
1034 return new _TimesMatcher(count);
1035 }
1036
1037 /** [happenedAtMost] matches a maximum number of calls. */
1038 Matcher happenedAtMost(count) {
1039 return new _TimesMatcher(0, count);
1040 }
1041
1042 /** [neverHappened] matches zero calls. */
1043 const Matcher neverHappened = const _TimesMatcher(0, 0);
1044
1045 /** [happenedOnce] matches exactly one call. */
1046 const Matcher happenedOnce = const _TimesMatcher(1, 1);
1047
1048 /** [happenedAtLeastOnce] matches one or more calls. */
1049 const Matcher happenedAtLeastOnce = const _TimesMatcher(1);
1050
1051 /** [happenedAtMostOnce] matches zero or one call. */
1052 const Matcher happenedAtMostOnce = const _TimesMatcher(0, 1);
1053
1054 /**
1055 * [_ResultMatcher]s are used to make assertions about the results
1056 * of method calls. These can be used as optional parameters to [getLogs].
1057 */
1058 class _ResultMatcher extends Matcher {
1059 final Action action;
1060 final Matcher value;
1061
1062 const _ResultMatcher(this.action, this.value);
1063
1064 bool matches(item, Map matchState) {
1065 if (item is! LogEntry) {
1066 return false;
1067 }
1068 // normalize the action; _PROXY is like _RETURN.
1069 Action eaction = item.action;
1070 if (eaction == Action.PROXY) {
1071 eaction = Action.RETURN;
1072 }
1073 return (eaction == action && value.matches(item.value, matchState));
1074 }
1075
1076 Description describe(Description description) {
1077 description.add(' to ');
1078 if (action == Action.RETURN || action == Action.PROXY)
1079 description.add('return ');
1080 else
1081 description.add('throw ');
1082 return description.addDescriptionOf(value);
1083 }
1084
1085 Description describeMismatch(item, Description mismatchDescription,
1086 Map matchState, bool verbose) {
1087 if (item.action == Action.RETURN || item.action == Action.PROXY) {
1088 mismatchDescription.add('returned ');
1089 } else {
1090 mismatchDescription.add('threw ');
1091 }
1092 mismatchDescription.add(item.value);
1093 return mismatchDescription;
1094 }
1095 }
1096
1097 /**
1098 *[returning] matches log entries where the call to a method returned
1099 * a value that matched [value].
1100 */
1101 Matcher returning(value) =>
1102 new _ResultMatcher(Action.RETURN, wrapMatcher(value));
1103
1104 /**
1105 *[throwing] matches log entrues where the call to a method threw
1106 * a value that matched [value].
1107 */
1108 Matcher throwing(value) =>
1109 new _ResultMatcher(Action.THROW, wrapMatcher(value));
1110
1111 /** Special values for use with [_ResultSetMatcher] [frequency]. */
1112 class _Frequency {
1113 /** Every call/throw must match */
1114 static const ALL = const _Frequency._('ALL');
1115
1116 /** At least one call/throw must match. */
1117 static const SOME = const _Frequency._('SOME');
1118
1119 /** No calls/throws should match. */
1120 static const NONE = const _Frequency._('NONE');
1121
1122 const _Frequency._(this.name);
1123
1124 final String name;
1125 }
1126
1127 /**
1128 * [_ResultSetMatcher]s are used to make assertions about the results
1129 * of method calls. When filtering an execution log by calling
1130 * [getLogs], a [LogEntrySet] of matching call logs is returned;
1131 * [_ResultSetMatcher]s can then assert various things about this
1132 * (sub)set of logs.
1133 *
1134 * We could make this class use _ResultMatcher but it doesn't buy that
1135 * match and adds some perf hit, so there is some duplication here.
1136 */
1137 class _ResultSetMatcher extends Matcher {
1138 final Action action;
1139 final Matcher value;
1140 final _Frequency frequency; // ALL, SOME, or NONE.
1141
1142 const _ResultSetMatcher(this.action, this.value, this.frequency);
1143
1144 bool matches(logList, Map matchState) {
1145 for (LogEntry entry in logList) {
1146 // normalize the action; PROXY is like RETURN.
1147 Action eaction = entry.action;
1148 if (eaction == Action.PROXY) {
1149 eaction = Action.RETURN;
1150 }
1151 if (eaction == action && value.matches(entry.value, matchState)) {
1152 if (frequency == _Frequency.NONE) {
1153 addStateInfo(matchState, {'entry': entry});
1154 return false;
1155 } else if (frequency == _Frequency.SOME) {
1156 return true;
1157 }
1158 } else {
1159 // Mismatch.
1160 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail.
1161 addStateInfo(matchState, {'entry': entry});
1162 return false;
1163 }
1164 }
1165 }
1166 // If we get here, then if count is _ALL we got all matches and
1167 // this is success; otherwise we got all mismatched which is
1168 // success for count == _NONE and failure for count == _SOME.
1169 return (frequency != _Frequency.SOME);
1170 }
1171
1172 Description describe(Description description) {
1173 description.add(' to ');
1174 description.add(frequency == _Frequency.ALL ? 'alway ' :
1175 (frequency == _Frequency.NONE ? 'never ' : 'sometimes '));
1176 if (action == Action.RETURN || action == Action.PROXY)
1177 description.add('return ');
1178 else
1179 description.add('throw ');
1180 return description.addDescriptionOf(value);
1181 }
1182
1183 Description describeMismatch(logList, Description mismatchDescription,
1184 Map matchState, bool verbose) {
1185 if (frequency != _Frequency.SOME) {
1186 LogEntry entry = matchState['entry'];
1187 if (entry.action == Action.RETURN || entry.action == Action.PROXY) {
1188 mismatchDescription.add('returned');
1189 } else {
1190 mismatchDescription.add('threw');
1191 }
1192 mismatchDescription.add(' value that ');
1193 value.describeMismatch(entry.value, mismatchDescription,
1194 matchState['state'], verbose);
1195 mismatchDescription.add(' at least once');
1196 } else {
1197 mismatchDescription.add('never did');
1198 }
1199 return mismatchDescription;
1200 }
1201 }
1202
1203 /**
1204 *[alwaysReturned] asserts that all matching calls to a method returned
1205 * a value that matched [value].
1206 */
1207 Matcher alwaysReturned(value) =>
1208 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.ALL);
1209
1210 /**
1211 *[sometimeReturned] asserts that at least one matching call to a method
1212 * returned a value that matched [value].
1213 */
1214 Matcher sometimeReturned(value) =>
1215 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.SOME);
1216
1217 /**
1218 *[neverReturned] asserts that no matching calls to a method returned
1219 * a value that matched [value].
1220 */
1221 Matcher neverReturned(value) =>
1222 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.NONE);
1223
1224 /**
1225 *[alwaysThrew] asserts that all matching calls to a method threw
1226 * a value that matched [value].
1227 */
1228 Matcher alwaysThrew(value) =>
1229 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.ALL);
1230
1231 /**
1232 *[sometimeThrew] asserts that at least one matching call to a method threw
1233 * a value that matched [value].
1234 */
1235 Matcher sometimeThrew(value) =>
1236 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.SOME);
1237
1238 /**
1239 *[neverThrew] asserts that no matching call to a method threw
1240 * a value that matched [value].
1241 */
1242 Matcher neverThrew(value) =>
1243 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.NONE);
1244
1245 /** The shared log used for named mocks. */
1246 LogEntryList sharedLog = null;
1247
1248 /** The base class for all mocked objects. */
1249 @proxy
1250 class Mock {
1251 /** The mock name. Needed if the log is shared; optional otherwise. */
1252 final String name;
1253
1254 /** The set of [Behavior]s supported. */
1255 final LinkedHashMap<String,Behavior> _behaviors;
1256
1257 /** How to handle unknown method calls - swallow or throw. */
1258 final bool _throwIfNoBehavior;
1259
1260 /** For spys, the real object that we are spying on. */
1261 final Object _realObject;
1262
1263 /** The [log] of calls made. Only used if [name] is null. */
1264 LogEntryList log;
1265
1266 /** Whether to create an audit log or not. */
1267 bool _logging;
1268
1269 bool get logging => _logging;
1270 set logging(bool value) {
1271 if (value && log == null) {
1272 log = new LogEntryList();
1273 }
1274 _logging = value;
1275 }
1276
1277 /**
1278 * Default constructor. Unknown method calls are allowed and logged,
1279 * the mock has no name, and has its own log.
1280 */
1281 Mock() :
1282 _throwIfNoBehavior = false, log = null, name = null, _realObject = null,
1283 _behaviors = new LinkedHashMap<String,Behavior>() {
1284 logging = true;
1285 }
1286
1287 /**
1288 * This constructor makes a mock that has a [name] and possibly uses
1289 * a shared [log]. If [throwIfNoBehavior] is true, any calls to methods
1290 * that have no defined behaviors will throw an exception; otherwise they
1291 * will be allowed and logged (but will not do anything).
1292 * If [enableLogging] is false, no logging will be done initially (whether
1293 * or not a [log] is supplied), but [logging] can be set to true later.
1294 */
1295 Mock.custom({this.name,
1296 this.log,
1297 throwIfNoBehavior: false,
1298 enableLogging: true})
1299 : _throwIfNoBehavior = throwIfNoBehavior, _realObject = null,
1300 _behaviors = new LinkedHashMap<String,Behavior>() {
1301 if (log != null && name == null) {
1302 throw new Exception("Mocks with shared logs must have a name.");
1303 }
1304 logging = enableLogging;
1305 }
1306
1307 /**
1308 * This constructor creates a spy with no user-defined behavior.
1309 * This is simply a proxy for a real object that passes calls
1310 * through to that real object but captures an audit trail of
1311 * calls made to the object that can be queried and validated
1312 * later.
1313 */
1314 Mock.spy(this._realObject, {this.name, this.log})
1315 : _behaviors = null,
1316 _throwIfNoBehavior = true {
1317 logging = true;
1318 }
1319
1320 /**
1321 * [when] is used to create a new or extend an existing [Behavior].
1322 * A [CallMatcher] [filter] must be supplied, and the [Behavior]s for
1323 * that signature are returned (being created first if needed).
1324 *
1325 * Typical use case:
1326 *
1327 * mock.when(callsTo(...)).alwaysReturn(...);
1328 */
1329 Behavior when(CallMatcher logFilter) {
1330 String key = logFilter.toString();
1331 if (!_behaviors.containsKey(key)) {
1332 Behavior b = new Behavior(logFilter);
1333 _behaviors[key] = b;
1334 return b;
1335 } else {
1336 return _behaviors[key];
1337 }
1338 }
1339
1340 /**
1341 * This is the handler for method calls. We loop through the list
1342 * of [Behavior]s, and find the first match that still has return
1343 * values available, and then do the action specified by that
1344 * return value. If we find no [Behavior] to apply an exception is
1345 * thrown.
1346 */
1347 noSuchMethod(Invocation invocation) {
1348 var method = MirrorSystem.getName(invocation.memberName);
1349 var args = invocation.positionalArguments;
1350 if (invocation.isGetter) {
1351 method = 'get $method';
1352 } else if (invocation.isSetter) {
1353 method = 'set $method';
1354 // Remove the trailing '='.
1355 if (method[method.length-1] == '=') {
1356 method = method.substring(0, method.length - 1);
1357 }
1358 }
1359 if (_behaviors == null) { // Spy.
1360 var mirror = reflect(_realObject);
1361 try {
1362 var result = mirror.delegate(invocation);
1363 log.add(new LogEntry(name, method, args, Action.PROXY, result));
1364 return result;
1365 } catch (e) {
1366 log.add(new LogEntry(name, method, args, Action.THROW, e));
1367 throw e;
1368 }
1369 }
1370 bool matchedMethodName = false;
1371 Map matchState = {};
1372 for (String k in _behaviors.keys) {
1373 Behavior b = _behaviors[k];
1374 if (b.matcher.nameFilter.matches(method, matchState)) {
1375 matchedMethodName = true;
1376 }
1377 if (b.matches(method, args)) {
1378 List actions = b.actions;
1379 if (actions == null || actions.length == 0) {
1380 continue; // No return values left in this Behavior.
1381 }
1382 // Get the first response.
1383 Responder response = actions[0];
1384 // If it is exhausted, remove it from the list.
1385 // Note that for endlessly repeating values, we started the count at
1386 // 0, so we get a potentially useful value here, which is the
1387 // (negation of) the number of times we returned the value.
1388 if (--response.count == 0) {
1389 actions.removeRange(0, 1);
1390 }
1391 // Do the response.
1392 Action action = response.action;
1393 var value = response.value;
1394 if (action == Action.RETURN) {
1395 if (_logging && b.logging) {
1396 log.add(new LogEntry(name, method, args, action, value));
1397 }
1398 return value;
1399 } else if (action == Action.THROW) {
1400 if (_logging && b.logging) {
1401 log.add(new LogEntry(name, method, args, action, value));
1402 }
1403 throw value;
1404 } else if (action == Action.PROXY) {
1405 // TODO(gram): Replace all this with:
1406 // var rtn = reflect(value).apply(invocation.positionalArguments,
1407 // invocation.namedArguments);
1408 // once that is supported.
1409 var rtn;
1410 switch (args.length) {
1411 case 0:
1412 rtn = value();
1413 break;
1414 case 1:
1415 rtn = value(args[0]);
1416 break;
1417 case 2:
1418 rtn = value(args[0], args[1]);
1419 break;
1420 case 3:
1421 rtn = value(args[0], args[1], args[2]);
1422 break;
1423 case 4:
1424 rtn = value(args[0], args[1], args[2], args[3]);
1425 break;
1426 case 5:
1427 rtn = value(args[0], args[1], args[2], args[3], args[4]);
1428 break;
1429 case 6:
1430 rtn = value(args[0], args[1], args[2], args[3],
1431 args[4], args[5]);
1432 break;
1433 case 7:
1434 rtn = value(args[0], args[1], args[2], args[3],
1435 args[4], args[5], args[6]);
1436 break;
1437 case 8:
1438 rtn = value(args[0], args[1], args[2], args[3],
1439 args[4], args[5], args[6], args[7]);
1440 break;
1441 case 9:
1442 rtn = value(args[0], args[1], args[2], args[3],
1443 args[4], args[5], args[6], args[7], args[8]);
1444 break;
1445 case 9:
1446 rtn = value(args[0], args[1], args[2], args[3],
1447 args[4], args[5], args[6], args[7], args[8], args[9]);
1448 break;
1449 default:
1450 throw new Exception(
1451 "Cannot proxy calls with more than 10 parameters.");
1452 }
1453 if (_logging && b.logging) {
1454 log.add(new LogEntry(name, method, args, action, rtn));
1455 }
1456 return rtn;
1457 }
1458 }
1459 }
1460 if (matchedMethodName) {
1461 // User did specify behavior for this method, but all the
1462 // actions are exhausted. This is considered an error.
1463 throw new Exception('No more actions for method '
1464 '${_qualifiedName(name, method)}.');
1465 } else if (_throwIfNoBehavior) {
1466 throw new Exception('No behavior specified for method '
1467 '${_qualifiedName(name, method)}.');
1468 }
1469 // Otherwise user hasn't specified behavior for this method; we don't throw
1470 // so we can underspecify.
1471 if (_logging) {
1472 log.add(new LogEntry(name, method, args, Action.IGNORE));
1473 }
1474 }
1475
1476 /** [verifyZeroInteractions] returns true if no calls were made */
1477 bool verifyZeroInteractions() {
1478 if (log == null) {
1479 // This means we created the mock with logging off and have never turned
1480 // it on, so it doesn't make sense to verify behavior on such a mock.
1481 throw new
1482 Exception("Can't verify behavior when logging was never enabled.");
1483 }
1484 return log.logs.length == 0;
1485 }
1486
1487 /**
1488 * [getLogs] extracts all calls from the call log that match the
1489 * [logFilter], and returns the matching list of [LogEntry]s. If
1490 * [destructive] is false (the default) the matching calls are left
1491 * in the log, else they are removed. Removal allows us to verify a
1492 * set of interactions and then verify that there are no other
1493 * interactions left. [actionMatcher] can be used to further
1494 * restrict the returned logs based on the action the mock performed.
1495 * [logFilter] can be a [CallMatcher] or a predicate function that
1496 * takes a [LogEntry] and returns a bool.
1497 *
1498 * Typical usage:
1499 *
1500 * getLogs(callsTo(...)).verify(...);
1501 */
1502 LogEntryList getLogs([CallMatcher logFilter,
1503 Matcher actionMatcher,
1504 bool destructive = false]) {
1505 if (log == null) {
1506 // This means we created the mock with logging off and have never turned
1507 // it on, so it doesn't make sense to get logs from such a mock.
1508 throw new
1509 Exception("Can't retrieve logs when logging was never enabled.");
1510 } else {
1511 return log.getMatches(name, logFilter, actionMatcher, destructive);
1512 }
1513 }
1514
1515 /**
1516 * Useful shorthand method that creates a [CallMatcher] from its arguments
1517 * and then calls [getLogs].
1518 */
1519 LogEntryList calls(method,
1520 [arg0 = _noArg,
1521 arg1 = _noArg,
1522 arg2 = _noArg,
1523 arg3 = _noArg,
1524 arg4 = _noArg,
1525 arg5 = _noArg,
1526 arg6 = _noArg,
1527 arg7 = _noArg,
1528 arg8 = _noArg,
1529 arg9 = _noArg]) =>
1530 getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4,
1531 arg5, arg6, arg7, arg8, arg9));
1532
1533 /** Clear the behaviors for the Mock. */
1534 void resetBehavior() => _behaviors.clear();
1535
1536 /** Clear the logs for the Mock. */
1537 void clearLogs() {
1538 if (log != null) {
1539 if (name == null) { // This log is not shared.
1540 log.logs.clear();
1541 } else { // This log may be shared.
1542 log.logs = log.logs.where((e) => e.mockName != name).toList();
1543 }
1544 }
1545 }
1546
1547 /** Clear both logs and behavior. */
1548 void reset() {
1549 resetBehavior();
1550 clearLogs();
1551 }
1552 }
OLDNEW
« no previous file with comments | « pkg/unittest/lib/mirror_matchers.dart ('k') | pkg/unittest/lib/src/core_matchers.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698