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

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

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

Powered by Google App Engine
This is Rietveld 408576698