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

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

Powered by Google App Engine
This is Rietveld 408576698