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

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

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

Powered by Google App Engine
This is Rietveld 408576698