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

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

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

Powered by Google App Engine
This is Rietveld 408576698