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

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

Issue 10804014: Temporal assertion support. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/lib/unittest/unittest_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /**
6 * The error formatter for mocking is a bit different from the default one 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' 7 * for unit testing; instead of the third argument being a 'reason'
8 * it is instead a [signature] describing the method signature filter 8 * it is instead a [signature] describing the method signature filter
9 * that was used to select the logs that were verified. 9 * that was used to select the logs that were verified.
10 */ 10 */
(...skipping 23 matching lines...) Expand all
34 } 34 }
35 35
36 _MockFailureHandler _mockFailureHandler = null; 36 _MockFailureHandler _mockFailureHandler = null;
37 37
38 /** 38 /**
39 * [_noArg] is a sentinel value representing no argument. 39 * [_noArg] is a sentinel value representing no argument.
40 */ 40 */
41 final _noArg = const _Sentinel(); 41 final _noArg = const _Sentinel();
42 42
43 /** The ways in which a call to a mock method can be handled. */ 43 /** The ways in which a call to a mock method can be handled. */
44 class _Action { 44 class Action {
45 /** Do nothing (void method) */ 45 /** Do nothing (void method) */
46 static final IGNORE = const _Action._('IGNORE'); 46 static final IGNORE = const Action._('IGNORE');
47 47
48 /** Return a supplied value. */ 48 /** Return a supplied value. */
49 static final RETURN = const _Action._('RETURN'); 49 static final RETURN = const Action._('RETURN');
50 50
51 /** Throw a supplied value. */ 51 /** Throw a supplied value. */
52 static final THROW = const _Action._('THROW'); 52 static final THROW = const Action._('THROW');
53 53
54 /** Call a supplied function. */ 54 /** Call a supplied function. */
55 static final PROXY = const _Action._('PROXY'); 55 static final PROXY = const Action._('PROXY');
56 56
57 const _Action._(this.name); 57 const Action._(this.name);
58 58
59 final String name; 59 final String name;
60 } 60 }
61 61
62 /** 62 /**
63 * The behavior of a method call in the mock library is specified 63 * The behavior of a method call in the mock library is specified
64 * with [Responder]s. A [Responder] has a [value] to throw 64 * with [Responder]s. A [Responder] has a [value] to throw
65 * or return (depending on whether [isThrow] is true or not, respectively), 65 * or return (depending on whether [isThrow] is true or not, respectively),
66 * and can either be one-shot, multi-shot, or infinitely repeating, 66 * and can either be one-shot, multi-shot, or infinitely repeating,
67 * depending on the value of [count (1, greater than 1, or 0 respectively). 67 * depending on the value of [count (1, greater than 1, or 0 respectively).
68 */ 68 */
69 class Responder { 69 class Responder {
70 var value; 70 var value;
71 _Action action; 71 Action action;
72 int count; 72 int count;
73 Responder(this.value, [this.count = 1, this.action = _Action.RETURN]); 73 Responder(this.value, [this.count = 1, this.action = Action.RETURN]);
74 } 74 }
75 75
76 /** 76 /**
77 * A [CallMatcher] is a special matcher used to match method calls (i.e. 77 * A [CallMatcher] is a special matcher used to match method calls (i.e.
78 * a method name and set of arguments). It is not a [Matcher] like the 78 * a method name and set of arguments). It is not a [Matcher] like the
79 * unit test [Matcher], but instead represents a method name and a 79 * unit test [Matcher], but instead represents a method name and a
80 * collection of [Matcher]s, one per argument, that will be applied 80 * collection of [Matcher]s, one per argument, that will be applied
81 * to the parameters to decide if the method call is a match. 81 * to the parameters to decide if the method call is a match.
82 */ 82 */
83 class CallMatcher { 83 class CallMatcher {
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
202 202
203 Behavior (this.matcher) { 203 Behavior (this.matcher) {
204 actions = new List<Responder>(); 204 actions = new List<Responder>();
205 } 205 }
206 206
207 /** 207 /**
208 * Adds a [Responder] that returns a [value] for [count] calls 208 * Adds a [Responder] that returns a [value] for [count] calls
209 * (1 by default). 209 * (1 by default).
210 */ 210 */
211 Behavior thenReturn(value, [count = 1]) { 211 Behavior thenReturn(value, [count = 1]) {
212 actions.add(new Responder(value, count, _Action.RETURN)); 212 actions.add(new Responder(value, count, Action.RETURN));
213 return this; // For chaining calls. 213 return this; // For chaining calls.
214 } 214 }
215 215
216 /** Adds a [Responder] that repeatedly returns a [value]. */ 216 /** Adds a [Responder] that repeatedly returns a [value]. */
217 Behavior alwaysReturn(value) { 217 Behavior alwaysReturn(value) {
218 return thenReturn(value, 0); 218 return thenReturn(value, 0);
219 } 219 }
220 220
221 /** 221 /**
222 * Adds a [Responder] that throws [value] [count] 222 * Adds a [Responder] that throws [value] [count]
223 * times (1 by default). 223 * times (1 by default).
224 */ 224 */
225 Behavior thenThrow(value, [count = 1]) { 225 Behavior thenThrow(value, [count = 1]) {
226 actions.add(new Responder(value, count, _Action.THROW)); 226 actions.add(new Responder(value, count, Action.THROW));
227 return this; // For chaining calls. 227 return this; // For chaining calls.
228 } 228 }
229 229
230 /** Adds a [Responder] that throws [value] endlessly. */ 230 /** Adds a [Responder] that throws [value] endlessly. */
231 Behavior alwaysThrow(value) { 231 Behavior alwaysThrow(value) {
232 return thenThrow(value, 0); 232 return thenThrow(value, 0);
233 } 233 }
234 234
235 /** 235 /**
236 * [thenCall] creates a proxy Responder, that is called [count] 236 * [thenCall] creates a proxy Responder, that is called [count]
237 * times (1 by default; 0 is used for unlimited calls, and is 237 * times (1 by default; 0 is used for unlimited calls, and is
238 * exposed as [alwaysCall]). [value] is the function that will 238 * exposed as [alwaysCall]). [value] is the function that will
239 * be called with the same arguments that were passed to the 239 * be called with the same arguments that were passed to the
240 * mock. Proxies can be used to wrap real objects or to define 240 * mock. Proxies can be used to wrap real objects or to define
241 * more complex return/throw behavior. You could even (if you 241 * more complex return/throw behavior. You could even (if you
242 * wanted) use proxies to emulate the behavior of thenReturn; 242 * wanted) use proxies to emulate the behavior of thenReturn;
243 * e.g.: 243 * e.g.:
244 * 244 *
245 * m.when(callsTo('foo')).thenReturn(0) 245 * m.when(callsTo('foo')).thenReturn(0)
246 * 246 *
247 * is equivalent to: 247 * is equivalent to:
248 * 248 *
249 * m.when(callsTo('foo')).thenCall(() => 0) 249 * m.when(callsTo('foo')).thenCall(() => 0)
250 */ 250 */
251 Behavior thenCall(value, [count = 1]) { 251 Behavior thenCall(value, [count = 1]) {
252 actions.add(new Responder(value, count, _Action.PROXY)); 252 actions.add(new Responder(value, count, Action.PROXY));
253 return this; // For chaining calls. 253 return this; // For chaining calls.
254 } 254 }
255 255
256 /** Creates a repeating proxy call. */ 256 /** Creates a repeating proxy call. */
257 Behavior alwaysCall(value) { 257 Behavior alwaysCall(value) {
258 return thenCall(value, 0); 258 return thenCall(value, 0);
259 } 259 }
260 260
261 /** Returns true if a method call matches the [Behavior]. */ 261 /** Returns true if a method call matches the [Behavior]. */
262 bool matches(String method, List args) => matcher.matches(method, args); 262 bool matches(String method, List args) => matcher.matches(method, args);
(...skipping 13 matching lines...) Expand all
276 /** The mock object name, if any. */ 276 /** The mock object name, if any. */
277 final String mockName; 277 final String mockName;
278 278
279 /** The method name. */ 279 /** The method name. */
280 final String methodName; 280 final String methodName;
281 281
282 /** The parameters. */ 282 /** The parameters. */
283 final List args; 283 final List args;
284 284
285 /** The behavior that resulted. */ 285 /** The behavior that resulted. */
286 final _Action action; 286 final Action action;
287 287
288 /** The value that was returned (if no throw). */ 288 /** The value that was returned (if no throw). */
289 final value; 289 final value;
290 290
291 LogEntry(this.mockName, this.methodName, 291 LogEntry(this.mockName, this.methodName,
292 this.args, this.action, [this.value]) { 292 this.args, this.action, [this.value]) {
293 time = new Date.now(); 293 time = new Date.now();
294 } 294 }
295 295
296 String _pad2(int val) => (val >= 10 ? '$val' : '0$val'); 296 String _pad2(int val) => (val >= 10 ? '$val' : '0$val');
297 297
298 String toString([Date baseTime]) { 298 String toString([Date baseTime]) {
299 Description d = new StringDescription(); 299 Description d = new StringDescription();
300 if (baseTime == null) { 300 if (baseTime == null) {
301 // Show absolute time. 301 // Show absolute time.
302 d.add('${time.hour}:${_pad2(time.minute)}:' 302 d.add('${time.hour}:${_pad2(time.minute)}:'
303 '${_pad2(time.second)}.${time.millisecond}> '); 303 '${_pad2(time.second)}.${time.millisecond}> ');
304 } else { 304 } else {
305 // Show relative time. 305 // Show relative time.
306 int delta = time.millisecondsSinceEpoch - baseTime.millisecondsSinceEpoch; 306 int delta = time.millisecondsSinceEpoch - baseTime.millisecondsSinceEpoch;
307 int secs = delta ~/ 1000; 307 int secs = delta ~/ 1000;
308 int msecs = delta % 1000; 308 int msecs = delta % 1000;
309 d.add('$secs.$msecs> '); 309 d.add('$secs.$msecs> ');
310 } 310 }
311 d.add('${_qualifiedName(mockName, methodName)}('); 311 d.add('${_qualifiedName(mockName, methodName)}(');
312 for (var i = 0; i < args.length; i++) { 312 for (var i = 0; i < args.length; i++) {
313 if (i != 0) d.add(', '); 313 if (i != 0) d.add(', ');
314 d.addDescriptionOf(args[i]); 314 d.addDescriptionOf(args[i]);
315 } 315 }
316 d.add(') ${action == _Action.THROW ? "threw" : "returned"} '); 316 d.add(') ${action == Action.THROW ? "threw" : "returned"} ');
317 d.addDescriptionOf(value); 317 d.addDescriptionOf(value);
318 return d.toString(); 318 return d.toString();
319 } 319 }
320 } 320 }
321 321
322 /** Utility function for optionally qualified method names */ 322 /** Utility function for optionally qualified method names */
323 String _qualifiedName(owner, String method) { 323 String _qualifiedName(owner, String method) {
324 if (owner == null || owner === anything) { 324 if (owner == null || owner === anything) {
325 return method; 325 return method;
326 } else if (owner is Matcher) { 326 } else if (owner is Matcher) {
327 Description d = new StringDescription(); 327 Description d = new StringDescription();
328 d.addDescriptionOf(owner); 328 d.addDescriptionOf(owner);
329 d.add('.'); 329 d.add('.');
330 d.add(method); 330 d.add(method);
331 return d.toString(); 331 return d.toString();
332 } else { 332 } else {
333 return '$owner.$method'; 333 return '$owner.$method';
334 } 334 }
335 } 335 }
336 336
337 /** 337 /**
338 * We do verification on a list of [LogEntry]s. To allow chaining 338 * We do verification on a list of [LogEntry]s. To allow chaining
339 * of calls to verify, we encapsulate such a list in the [LogEntryList] 339 * of calls to verify, we encapsulate such a list in the [LogEntryList]
340 * class. 340 * class.
341 */ 341 */
342 class LogEntryList { 342 class LogEntryList {
343 final String filter; 343 String filter;
344 List<LogEntry> logs; 344 List<LogEntry> logs;
345 LogEntryList([this.filter]) { 345 LogEntryList([this.filter]) {
346 logs = new List<LogEntry>(); 346 logs = new List<LogEntry>();
347 } 347 }
348 348
349 /** Add a [LogEntry] to the log. */ 349 /** Add a [LogEntry] to the log. */
350 add(LogEntry entry) => logs.add(entry); 350 add(LogEntry entry) => logs.add(entry);
351 351
352 /** Get the first entry, or null if no entries. */
353 get first() => (logs == null || logs.length == 0) ? null : logs[0];
354
355 /** Get the last entry, or null if no entries. */
356 get last() => (logs == null || logs.length == 0) ? null : logs.last();
357
358 /** Creates a LogEntry predicate function from the argument. */
359 Function _makePredicate(arg) {
360 if (arg == null) {
361 return (e) => true;
362 } else if (arg is CallMatcher) {
363 return (e) => arg.matches(e.methodName, e.args);
364 } else if (arg is Function) {
365 return arg;
366 } else {
367 throw new Exception("Invalid argument to _makePredicate.");
368 }
369 }
370
352 /** 371 /**
353 * Create a new [LogEntryList] consisting of [LogEntry]s from 372 * Create a new [LogEntryList] consisting of [LogEntry]s from
354 * this list that match the specified [mockNameFilter] and [logFilter]. 373 * this list that match the specified [mockNameFilter] and [logFilter].
355 * [mockNameFilter] can be null, a [String], a predicate [Function], 374 * [mockNameFilter] can be null, a [String], a predicate [Function],
356 * or a [Matcher]. If [mockNameFilter] is null, this is the same as 375 * or a [Matcher]. If [mockNameFilter] is null, this is the same as
357 * [anything]. 376 * [anything].
358 * If [logFilter] is null, all entries in the log will be returned. 377 * If [logFilter] is null, all entries in the log will be returned.
378 * Otherwise [logFilter] should be a [CallMatcher] or predicate function
379 * that takes a [LogEntry] and returns a bool.
359 * If [destructive] is true, the log entries are removed from the 380 * If [destructive] is true, the log entries are removed from the
360 * original list. 381 * original list.
361 */ 382 */
362 LogEntryList getMatches([mockNameFilter, 383 LogEntryList getMatches([mockNameFilter,
363 CallMatcher logFilter, 384 logFilter,
364 Matcher actionMatcher, 385 Matcher actionMatcher,
365 bool destructive = false]) { 386 bool destructive = false]) {
366 if (mockNameFilter == null) { 387 if (mockNameFilter == null) {
367 mockNameFilter = anything; 388 mockNameFilter = anything;
368 } else { 389 } else {
369 mockNameFilter = wrapMatcher(mockNameFilter); 390 mockNameFilter = wrapMatcher(mockNameFilter);
370 } 391 }
371 if (logFilter == null) { 392 Function entryFilter = _makePredicate(logFilter);
372 logFilter = new CallMatcher();
373 }
374 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); 393 String filterName = _qualifiedName(mockNameFilter, logFilter.toString());
375 LogEntryList rtn = new LogEntryList(filterName); 394 LogEntryList rtn = new LogEntryList(filterName);
376 for (var i = 0; i < logs.length; i++) { 395 for (var i = 0; i < logs.length; i++) {
377 LogEntry entry = logs[i]; 396 LogEntry entry = logs[i];
378 if (!mockNameFilter.matches(entry.mockName)) { 397 if (mockNameFilter.matches(entry.mockName) && entryFilter(entry)) {
379 continue;
380 }
381 if (logFilter.matches(entry.methodName, entry.args)) {
382 if (actionMatcher == null || actionMatcher.matches(entry)) { 398 if (actionMatcher == null || actionMatcher.matches(entry)) {
383 rtn.add(entry); 399 rtn.add(entry);
384 if (destructive) { 400 if (destructive) {
385 logs.removeRange(i--, 1); 401 logs.removeRange(i--, 1);
386 } 402 }
387 } 403 }
388 } 404 }
389 } 405 }
390 return rtn; 406 return rtn;
391 } 407 }
392 408
393 /** Apply a unit test [Matcher] to the [LogEntryList]. */ 409 /** Apply a unit test [Matcher] to the [LogEntryList]. */
394 LogEntryList verify(Matcher matcher) { 410 LogEntryList verify(Matcher matcher) {
395 if (_mockFailureHandler == null) { 411 if (_mockFailureHandler == null) {
396 _mockFailureHandler = 412 _mockFailureHandler =
397 new _MockFailureHandler(getOrCreateExpectFailureHandler()); 413 new _MockFailureHandler(getOrCreateExpectFailureHandler());
398 } 414 }
399 expect(logs, matcher, filter, _mockFailureHandler); 415 expect(logs, matcher, filter, _mockFailureHandler);
400 return this; 416 return this;
401 } 417 }
402 418
419 /**
420 * Turn the logs into human-readable text. If [baseTime] is specified
421 * then each entry is prefixed with the offset from that time in
422 * milliseconds; otherwise the time of day is used.
423 */
403 String toString([Date baseTime]) { 424 String toString([Date baseTime]) {
404 String s = ''; 425 String s = '';
405 for (var e in logs) { 426 for (var e in logs) {
406 s = '$s${e.toString(baseTime)}\n'; 427 s = '$s${e.toString(baseTime)}\n';
407 } 428 }
408 return s; 429 return s;
409 } 430 }
431
432 /**
433 * Find the first log entry that satisfies [logFilter] and
434 * return its position. A search [start] position can be provided
435 * to allow for repeated searches. [logFilter] can be a [CallMatcher],
436 * or a predicate function that takes a [LogEntry] argument and returns
437 * a bool. If [logFilter] is null, it will match any [LogEntry].
438 * If no entry is found, then [failureReturnValue] is returned.
439 */
440 int findLogEntry(logFilter, [int start = 0, int failureReturnValue = -1]) {
441 logFilter = _makePredicate(logFilter);
442 int pos = start;
443 while (pos < logs.length) {
444 if (logFilter(logs[pos])) {
445 return pos;
446 }
447 ++pos;
448 }
449 return failureReturnValue;
450 }
451
452 /**
453 * Returns log events that happened up to the first one that
454 * satisfies [logFilter]. If [inPlace] is true, then returns
455 * this LogEntryList after removing the from the first satisfier;
456 * onwards otherwise a new list is created. [description]
457 * is used to create a new name for the resulting list.
458 * [defaultPosition] is used as the index of the matching item in
459 * the case that no match is found.
460 */
461 LogEntryList _head(logFilter, bool inPlace,
462 String description, int defaultPosition) {
463 if (filter != null) {
464 description = '$filter $description';
465 }
466 int pos = findLogEntry(logFilter, 0, defaultPosition);
467 if (inPlace) {
468 if (pos < logs.length) {
469 logs.removeRange(pos, logs.length - pos);
470 }
471 filter = description;
472 return this;
473 } else {
474 LogEntryList newList = new LogEntryList(description);
475 for (var i = 0; i < pos; i++) {
476 newList.logs.add(logs[i]);
477 }
478 return newList;
479 }
480 }
481
482 /**
483 * Returns log events that happened from the first one that
484 * satisfies [logFilter]. If [inPlace] is true, then returns
485 * this LogEntryList after removing the entries up to the first
486 * satisfier; otherwise a new list is created. [description]
487 * is used to create a new name for the resulting list.
488 * [defaultPosition] is used as the index of the matching item in
489 * the case that no match is found.
490 */
491 LogEntryList _tail(logFilter, bool inPlace,
492 String description, int defaultPosition) {
493 if (filter != null) {
494 description = '$filter $description';
495 }
496 int pos = findLogEntry(logFilter, 0, defaultPosition);
497 if (inPlace) {
498 if (pos > 0) {
499 logs.removeRange(0, pos);
500 }
501 filter = description;
502 return this;
503 } else {
504 LogEntryList newList = new LogEntryList(description);
505 while (pos < logs.length) {
506 newList.logs.add(logs[pos++]);
507 }
508 return newList;
509 }
510 }
511
512 /**
513 * Returns log events that happened after [when]. If [inPlace]
514 * is true, then it returns this LogEntryList after removing
515 * the entries that happened up to [when]; otherwise a new
516 * list is created.
517 */
518 LogEntryList after(Date when, [bool inPlace = false]) =>
519 _tail((e) => e.time > when, inPlace, 'after $when', logs.length);
520
521 /**
522 * Returns log events that happened from [when] onwards. If
523 * [inPlace] is true, then it returns this LogEntryList after
524 * removing the entries that happened before [when]; otherwise
525 * a new list is created.
526 */
527 LogEntryList from(Date when, [bool inPlace = false]) =>
528 _tail((e) => e.time >= when, inPlace, 'from $when', logs.length);
529
530 /**
531 * Returns log events that happened until [when]. If [inPlace]
532 * is true, then it returns this LogEntryList after removing
533 * the entries that happened after [when]; otherwise a new
534 * list is created.
535 */
536 LogEntryList until(Date when, [bool inPlace = false]) =>
537 _head((e) => e.time > when, inPlace, 'until $when', logs.length);
538
539 /**
540 * Returns log events that happened before [when]. If [inPlace]
541 * is true, then it returns this LogEntryList after removing
542 * the entries that happened from [when] onwards; otherwise a new
543 * list is created.
544 */
545 LogEntryList before(Date when, [bool inPlace = false]) =>
546 _head((e) => e.time >= when, inPlace, 'before $when', logs.length);
547
548 /**
549 * Returns log events that happened after [logEntry]'s time.
550 * If [inPlace] is true, then it returns this LogEntryList after
551 * removing the entries that happened up to [when]; otherwise a new
552 * list is created. If [logEntry] is null the current time is used.
553 */
554 LogEntryList afterEntry(LogEntry logEntry, [bool inPlace = false]) =>
555 after(logEntry == null ? new Date.now() : logEntry.time);
556
557 /**
558 * Returns log events that happened from [logEntry]'s time onwards.
559 * If [inPlace] is true, then it returns this LogEntryList after
560 * removing the entries that happened before [when]; otherwise
561 * a new list is created. If [logEntry] is null the current time is used.
562 */
563 LogEntryList fromEntry(LogEntry logEntry, [bool inPlace = false]) =>
564 from(logEntry == null ? new Date.now() : logEntry.time);
565
566 /**
567 * Returns log events that happened until [logEntry]'s time. If
568 * [inPlace] is true, then it returns this LogEntryList after removing
569 * the entries that happened after [when]; otherwise a new
570 * list is created. If [logEntry] is null the epoch time is used.
571 */
572 LogEntryList untilEntry(LogEntry logEntry, [bool inPlace = false]) =>
573 until(logEntry == null ?
574 new Date.fromMillisecondsSinceEpoch(0) : logEntry.time);
575
576 /**
577 * Returns log events that happened before [logEntry]'s time. If
578 * [inPlace] is true, then it returns this LogEntryList after removing
579 * the entries that happened from [when] onwards; otherwise a new
580 * list is created. If [logEntry] is null the epoch time is used.
581 */
582 LogEntryList beforeEntry(LogEntry logEntry, [bool inPlace = false]) =>
583 before(logEntry == null ?
584 new Date.fromMillisecondsSinceEpoch(0) : logEntry.time);
585
586 /**
587 * Returns log events that happened after the first event in [segment].
588 * If [inPlace] is true, then it returns this LogEntryList after removing
589 * the entries that happened earlier; otherwise a new list is created.
590 */
591 LogEntryList afterFirst(LogEntryList segment, [bool inPlace = false]) =>
592 afterEntry(segment.first, inPlace);
593
594 /**
595 * Returns log events that happened after the last event in [segment].
596 * If [inPlace] is true, then it returns this LogEntryList after removing
597 * the entries that happened earlier; otherwise a new list is created.
598 */
599 LogEntryList afterLast(LogEntryList segment, [bool inPlace = false]) =>
600 afterEntry(segment.last, inPlace);
601
602 /**
603 * Returns log events that happened from the time of the first event in
604 * [segment] onwards. If [inPlace] is true, then it returns this
605 * LogEntryList after removing the earlier entries; otherwise a new list
606 * is created.
607 */
608 LogEntryList fromFirst(LogEntryList segment, [bool inPlace = false]) =>
609 fromEntry(segment.first, inPlace);
610
611 /**
612 * Returns log events that happened from the time of the last event in
613 * [segment] onwards. If [inPlace] is true, then it returns this
614 * LogEntryList after removing the earlier entries; otherwise a new list
615 * is created.
616 */
617 LogEntryList fromLast(LogEntryList segment, [bool inPlace = false]) =>
618 fromEntry(segment.last, inPlace);
619
620 /**
621 * Returns log events that happened until the first event in [segment].
622 * If [inPlace] is true, then it returns this LogEntryList after removing
623 * the entries that happened later; otherwise a new list is created.
624 */
625 LogEntryList untilFirst(LogEntryList segment, [bool inPlace = false]) =>
626 untilEntry(segment.first, inPlace);
627
628 /**
629 * Returns log events that happened until the last event in [segment].
630 * If [inPlace] is true, then it returns this LogEntryList after removing
631 * the entries that happened later; otherwise a new list is created.
632 */
633 LogEntryList untilLast(LogEntryList segment, [bool inPlace = false]) =>
634 untilEntry(segment.last, inPlace);
635
636 /**
637 * Returns log events that happened before the first event in [segment].
638 * If [inPlace] is true, then it returns this LogEntryList after removing
639 * the entries that happened later; otherwise a new list is created.
640 */
641 LogEntryList beforeFirst(LogEntryList segment, [bool inPlace = false]) =>
642 beforeEntry(segment.first, inPlace);
643
644 /**
645 * Returns log events that happened before the last event in [segment].
646 * If [inPlace] is true, then it returns this LogEntryList after removing
647 * the entries that happened later; otherwise a new list is created.
648 */
649 LogEntryList beforeLast(LogEntryList segment, [bool inPlace = false]) =>
650 beforeEntry(segment.last, inPlace);
651
652 /**
653 * Iterate through the LogEntryList looking for matches to the entries
654 * in [keys]; for each match found the closest [distance] neighboring log
655 * entries that match [mockNameFilter] and [logFilter] will be included in
656 * the result. If [isPreceding] is true we use the neighbors that precede
657 * the matched entry; else we use the neighbors that followed.
658 * If [includeKeys] is true then the entries in [keys] that resulted in
659 * entries in the output list are themselves included in the output list. If
660 * [distance] is zero then all matches are included.
661 */
662 LogEntryList _neighboring(bool isPreceding,
663 LogEntryList keys,
664 mockNameFilter,
665 logFilter,
666 int distance,
667 bool includeKeys) {
668 LogEntryList rtn = new LogEntryList();
669
670 // Deal with the trivial case.
671 if (logs.length == 0 || keys.logs.length == 0) {
672 return rtn;
673 }
674
675 // Normalize the mockNameFilter and logFilter values.
676 if (mockNameFilter == null) {
677 mockNameFilter = anything;
678 } else {
679 mockNameFilter = wrapMatcher(mockNameFilter);
680 }
681 logFilter = _makePredicate(logFilter);
682
683 // The scratch list is used to hold matching entries when we
684 // are doing preceding neighbors. The remainingCount is used to
685 // keep track of how many matching entries we can still add in the
686 // current segment (0 if we are doing doing following neighbors, until
687 // we get our first key match).
688 List scratch = null;
689 int remainingCount = 0;
690 if (isPreceding) {
691 scratch = new List();
692 remainingCount = logs.length;
693 }
694
695 var keyIterator = keys.logs.iterator();
696 LogEntry keyEntry = keyIterator.next();
697
698 for (LogEntry logEntry in logs) {
699 // If we have a log entry match, copy the saved matches from the
700 // scratch buffer into the return list, as well as the matching entry,
701 // if appropriate, and reset the scratch buffer. Continue processing
702 // from the next key entry.
703 if (keyEntry == logEntry) {
704 if (scratch != null) {
705 int numToCopy = scratch.length;
706 if (distance > 0 && distance < numToCopy) {
707 numToCopy = distance;
708 }
709 for (var i = scratch.length - numToCopy; i < scratch.length; i++) {
710 rtn.logs.add(scratch[i]);
711 }
712 scratch.clear();
713 } else {
714 remainingCount = distance > 0 ? distance : logs.length;
715 }
716 if (includeKeys) {
717 rtn.logs.add(keyEntry);
718 }
719 if (keyIterator.hasNext()) {
720 keyEntry = keyIterator.next();
721 } else if (isPreceding) { // We're done.
722 break;
723 }
724 } else if (remainingCount > 0 &&
725 mockNameFilter.matches(logEntry.mockName) &&
726 logFilter(logEntry)) {
727 if (scratch != null) {
728 scratch.add(logEntry);
729 } else {
730 rtn.logs.add(logEntry);
731 --remainingCount;
732 }
733 }
734 }
735 return rtn;
736 }
737
738 /**
739 * Iterate through the LogEntryList looking for matches to the entries
740 * in [keys]; for each match found the closest [distance] prior log entries
741 * that match [mocknameFilter] and [logFilter] will be included in the result.
742 * If [includeKeys] is true then the entries in [keys] that resulted in
743 * entries in the output list are themselves included in the output list. If
744 * [distance] is zero then all matches are included.
745 *
746 * The idea here is that you could find log entries that are related to
747 * other logs entries in some temporal sense. For example, say we have a
748 * method commit() that returns -1 on failure. Before commit() gets called
749 * the value being committed is created by process(). We may want to find
750 * the calls to process() that preceded calls to commit() that failed.
751 * We could do this with:
752 *
753 * print(log.preceding(log.getLogs(callsTo('commit'), returning(-1)),
754 * logFilter: callsTo('process')).toString());
Siggi Cherem (dart-lang) 2012/07/19 20:02:25 Awesome example! The print/toString was slighly di
755 *
756 * We might want to include the details of the failing calls to commit()
757 * to see what parameters were passed in, in which case we would set
758 * [includeKeys].
759 *
760 * As another simple example, say we wanted to know the three method
761 * calls that immediately preceded each failing call to commit():
762 *
763 * print(log.preceding(log.getLogs(callsTo('commit'), returning(-1)),
764 * distance: 3).toString());
Siggi Cherem (dart-lang) 2012/07/19 20:02:25 ditto
765 */
766 LogEntryList preceding(LogEntryList keys,
767 [mockNameFilter = null,
768 logFilter = null,
769 int distance = 1,
770 bool includeKeys = false]) =>
771 _neighboring(true, keys, mockNameFilter, logFilter,
772 distance, includeKeys);
773
774 /**
775 * Iterate through the LogEntryList looking for matches to the entries
776 * in [keys]; for each match found the closest [distance] subsequent log
777 * entries that match [mocknameFilter] and [logFilter] will be included in
778 * the result. If [includeKeys] is true then the entries in [keys] that
779 * resulted in entries in the output list are themselves included in the
780 * output list. If [distance] is zero then all matches are included.
781 * See [preceding] for a usage example.
782 */
783 LogEntryList following(LogEntryList keys,
784 [mockNameFilter = null,
785 logFilter = null,
786 int distance = 1,
787 bool includeKeys = false]) =>
788 _neighboring(false, keys, mockNameFilter, logFilter,
789 distance, includeKeys);
410 } 790 }
411 791
412 /** 792 /**
413 * [_TimesMatcher]s are used to make assertions about the number of 793 * [_TimesMatcher]s are used to make assertions about the number of
414 * times a method was called. 794 * times a method was called.
415 */ 795 */
416 class _TimesMatcher extends BaseMatcher { 796 class _TimesMatcher extends BaseMatcher {
417 final int min, max; 797 final int min, max;
418 798
419 const _TimesMatcher(this.min, [this.max = -1]); 799 const _TimesMatcher(this.min, [this.max = -1]);
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
463 final Matcher happenedAtLeastOnce = const _TimesMatcher(1); 843 final Matcher happenedAtLeastOnce = const _TimesMatcher(1);
464 844
465 /** [happenedAtMostOnce] matches zero or one call. */ 845 /** [happenedAtMostOnce] matches zero or one call. */
466 final Matcher happenedAtMostOnce = const _TimesMatcher(0, 1); 846 final Matcher happenedAtMostOnce = const _TimesMatcher(0, 1);
467 847
468 /** 848 /**
469 * [_ResultMatcher]s are used to make assertions about the results 849 * [_ResultMatcher]s are used to make assertions about the results
470 * of method calls. These can be used as optional parameters to [getLogs]. 850 * of method calls. These can be used as optional parameters to [getLogs].
471 */ 851 */
472 class _ResultMatcher extends BaseMatcher { 852 class _ResultMatcher extends BaseMatcher {
473 final _Action action; 853 final Action action;
474 final Matcher value; 854 final Matcher value;
475 855
476 const _ResultMatcher(this.action, this.value); 856 const _ResultMatcher(this.action, this.value);
477 857
478 bool matches(item) { 858 bool matches(item) {
479 if (item is! LogEntry) { 859 if (item is! LogEntry) {
480 return false; 860 return false;
481 } 861 }
482 // normalize the action; _PROXY is like _RETURN. 862 // normalize the action; _PROXY is like _RETURN.
483 _Action eaction = item.action; 863 Action eaction = item.action;
484 if (eaction == _Action.PROXY) { 864 if (eaction == Action.PROXY) {
485 eaction = _Action.RETURN; 865 eaction = Action.RETURN;
486 } 866 }
487 return (eaction == action && value.matches(item.value)); 867 return (eaction == action && value.matches(item.value));
488 } 868 }
489 869
490 Description describe(Description description) { 870 Description describe(Description description) {
491 description.add(' to '); 871 description.add(' to ');
492 if (action == _Action.RETURN || action == _Action.PROXY) 872 if (action == Action.RETURN || action == Action.PROXY)
493 description.add('return '); 873 description.add('return ');
494 else 874 else
495 description.add('throw '); 875 description.add('throw ');
496 return description.addDescriptionOf(value); 876 return description.addDescriptionOf(value);
497 } 877 }
498 878
499 Description describeMismatch(item, Description mismatchDescription) { 879 Description describeMismatch(item, Description mismatchDescription) {
500 if (item.action == _Action.RETURN || item.action == _Action.PROXY) { 880 if (item.action == Action.RETURN || item.action == Action.PROXY) {
501 mismatchDescription.add('returned '); 881 mismatchDescription.add('returned ');
502 } else { 882 } else {
503 mismatchDescription.add('threw '); 883 mismatchDescription.add('threw ');
504 } 884 }
505 mismatchDescription.add(item.value); 885 mismatchDescription.add(item.value);
506 return mismatchDescription; 886 return mismatchDescription;
507 } 887 }
508 } 888 }
509 889
510 /** 890 /**
511 *[returning] matches log entries where the call to a method returned 891 *[returning] matches log entries where the call to a method returned
512 * a value that matched [value]. 892 * a value that matched [value].
513 */ 893 */
514 Matcher returning(value) => 894 Matcher returning(value) =>
515 new _ResultMatcher(_Action.RETURN, wrapMatcher(value)); 895 new _ResultMatcher(Action.RETURN, wrapMatcher(value));
516 896
517 /** 897 /**
518 *[throwing] matches log entrues where the call to a method threw 898 *[throwing] matches log entrues where the call to a method threw
519 * a value that matched [value]. 899 * a value that matched [value].
520 */ 900 */
521 Matcher throwing(value) => 901 Matcher throwing(value) =>
522 new _ResultMatcher(_Action.THROW, wrapMatcher(value)); 902 new _ResultMatcher(Action.THROW, wrapMatcher(value));
523 903
524 /** Special values for use with [_ResultSetMatcher] [frequency]. */ 904 /** Special values for use with [_ResultSetMatcher] [frequency]. */
525 class _Frequency { 905 class _Frequency {
526 /** Every call/throw must match */ 906 /** Every call/throw must match */
527 static final ALL = const _Frequency._('ALL'); 907 static final ALL = const _Frequency._('ALL');
528 908
529 /** At least one call/throw must match. */ 909 /** At least one call/throw must match. */
530 static final SOME = const _Frequency._('SOME'); 910 static final SOME = const _Frequency._('SOME');
531 911
532 /** No calls/throws should match. */ 912 /** No calls/throws should match. */
533 static final NONE = const _Frequency._('NONE'); 913 static final NONE = const _Frequency._('NONE');
534 914
535 const _Frequency._(this.name); 915 const _Frequency._(this.name);
536 916
537 final String name; 917 final String name;
538 } 918 }
539 919
540 /** 920 /**
541 * [_ResultSetMatcher]s are used to make assertions about the results 921 * [_ResultSetMatcher]s are used to make assertions about the results
542 * of method calls. When filtering an execution log by calling 922 * of method calls. When filtering an execution log by calling
543 * [getLogs], a [LogEntrySet] of matching call logs is returned; 923 * [getLogs], a [LogEntrySet] of matching call logs is returned;
544 * [_ResultSetMatcher]s can then assert various things about this 924 * [_ResultSetMatcher]s can then assert various things about this
545 * (sub)set of logs. 925 * (sub)set of logs.
546 * 926 *
547 * We could make this class use _ResultMatcher but it doesn't buy that 927 * We could make this class use _ResultMatcher but it doesn't buy that
548 * match and adds some perf hit, so there is some duplication here. 928 * match and adds some perf hit, so there is some duplication here.
549 */ 929 */
550 class _ResultSetMatcher extends BaseMatcher { 930 class _ResultSetMatcher extends BaseMatcher {
551 final _Action action; 931 final Action action;
552 final Matcher value; 932 final Matcher value;
553 final _Frequency frequency; // ALL, SOME, or NONE. 933 final _Frequency frequency; // ALL, SOME, or NONE.
554 934
555 const _ResultSetMatcher(this.action, this.value, this.frequency); 935 const _ResultSetMatcher(this.action, this.value, this.frequency);
556 936
557 bool matches(log) { 937 bool matches(log) {
558 for (LogEntry entry in log) { 938 for (LogEntry entry in log) {
559 // normalize the action; _PROXY is like _RETURN. 939 // normalize the action; PROXY is like RETURN.
560 _Action eaction = entry.action; 940 Action eaction = entry.action;
561 if (eaction == _Action.PROXY) { 941 if (eaction == Action.PROXY) {
562 eaction = _Action.RETURN; 942 eaction = Action.RETURN;
563 } 943 }
564 if (eaction == action && value.matches(entry.value)) { 944 if (eaction == action && value.matches(entry.value)) {
565 if (frequency == _Frequency.NONE) { 945 if (frequency == _Frequency.NONE) {
566 return false; 946 return false;
567 } else if (frequency == _Frequency.SOME) { 947 } else if (frequency == _Frequency.SOME) {
568 return true; 948 return true;
569 } 949 }
570 } else { 950 } else {
571 // Mismatch. 951 // Mismatch.
572 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. 952 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail.
573 return false; 953 return false;
574 } 954 }
575 } 955 }
576 } 956 }
577 // If we get here, then if count is _ALL we got all matches and 957 // If we get here, then if count is _ALL we got all matches and
578 // this is success; otherwise we got all mismatched which is 958 // this is success; otherwise we got all mismatched which is
579 // success for count == _NONE and failure for count == _SOME. 959 // success for count == _NONE and failure for count == _SOME.
580 return (frequency != _Frequency.SOME); 960 return (frequency != _Frequency.SOME);
581 } 961 }
582 962
583 Description describe(Description description) { 963 Description describe(Description description) {
584 description.add(' to '); 964 description.add(' to ');
585 description.add(frequency == _Frequency.ALL ? 'alway ' : 965 description.add(frequency == _Frequency.ALL ? 'alway ' :
586 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); 966 (frequency == _Frequency.NONE ? 'never ' : 'sometimes '));
587 if (action == _Action.RETURN || action == __Action.PROXY) 967 if (action == Action.RETURN || action == Action.PROXY)
588 description.add('return '); 968 description.add('return ');
589 else 969 else
590 description.add('throw '); 970 description.add('throw ');
591 return description.addDescriptionOf(value); 971 return description.addDescriptionOf(value);
592 } 972 }
593 973
594 Description describeMismatch(log, Description mismatchDescription) { 974 Description describeMismatch(log, Description mismatchDescription) {
595 if (frequency != _Frequency.SOME) { 975 if (frequency != _Frequency.SOME) {
596 for (LogEntry entry in log) { 976 for (LogEntry entry in log) {
597 if (entry.action != action || !value.matches(entry.value)) { 977 if (entry.action != action || !value.matches(entry.value)) {
598 if (entry.action == _Action.RETURN || entry.action == _Action.PROXY) 978 if (entry.action == Action.RETURN || entry.action == Action.PROXY)
599 mismatchDescription.add('returned '); 979 mismatchDescription.add('returned ');
600 else 980 else
601 mismatchDescription.add('threw '); 981 mismatchDescription.add('threw ');
602 mismatchDescription.add(entry.value); 982 mismatchDescription.add(entry.value);
603 mismatchDescription.add(' at least once'); 983 mismatchDescription.add(' at least once');
604 break; 984 break;
605 } 985 }
606 } 986 }
607 } else { 987 } else {
608 mismatchDescription.add('never did'); 988 mismatchDescription.add('never did');
609 } 989 }
610 return mismatchDescription; 990 return mismatchDescription;
611 } 991 }
612 } 992 }
613 993
614 /** 994 /**
615 *[alwaysReturned] asserts that all matching calls to a method returned 995 *[alwaysReturned] asserts that all matching calls to a method returned
616 * a value that matched [value]. 996 * a value that matched [value].
617 */ 997 */
618 Matcher alwaysReturned(value) => 998 Matcher alwaysReturned(value) =>
619 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.ALL); 999 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.ALL);
620 1000
621 /** 1001 /**
622 *[sometimeReturned] asserts that at least one matching call to a method 1002 *[sometimeReturned] asserts that at least one matching call to a method
623 * returned a value that matched [value]. 1003 * returned a value that matched [value].
624 */ 1004 */
625 Matcher sometimeReturned(value) => 1005 Matcher sometimeReturned(value) =>
626 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.SOME); 1006 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.SOME);
627 1007
628 /** 1008 /**
629 *[neverReturned] asserts that no matching calls to a method returned 1009 *[neverReturned] asserts that no matching calls to a method returned
630 * a value that matched [value]. 1010 * a value that matched [value].
631 */ 1011 */
632 Matcher neverReturned(value) => 1012 Matcher neverReturned(value) =>
633 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.NONE); 1013 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.NONE);
634 1014
635 /** 1015 /**
636 *[alwaysThrew] asserts that all matching calls to a method threw 1016 *[alwaysThrew] asserts that all matching calls to a method threw
637 * a value that matched [value]. 1017 * a value that matched [value].
638 */ 1018 */
639 Matcher alwaysThrew(value) => 1019 Matcher alwaysThrew(value) =>
640 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.ALL); 1020 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.ALL);
641 1021
642 /** 1022 /**
643 *[sometimeThrew] asserts that at least one matching call to a method threw 1023 *[sometimeThrew] asserts that at least one matching call to a method threw
644 * a value that matched [value]. 1024 * a value that matched [value].
645 */ 1025 */
646 Matcher sometimeThrew(value) => 1026 Matcher sometimeThrew(value) =>
647 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.SOME); 1027 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.SOME);
648 1028
649 /** 1029 /**
650 *[neverThrew] asserts that no matching call to a method threw 1030 *[neverThrew] asserts that no matching call to a method threw
651 * a value that matched [value]. 1031 * a value that matched [value].
652 */ 1032 */
653 Matcher neverThrew(value) => 1033 Matcher neverThrew(value) =>
654 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.NONE); 1034 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.NONE);
655 1035
656 /** The shared log used for named mocks. */ 1036 /** The shared log used for named mocks. */
657 LogEntryList sharedLog = null; 1037 LogEntryList sharedLog = null;
658 1038
659 /** 1039 /**
660 * [Mock] is the base class for all mocked objects, with 1040 * [Mock] is the base class for all mocked objects, with
661 * support for basic mocking. 1041 * support for basic mocking.
662 * 1042 *
663 * To create a mock objects for some class T, create a new class using: 1043 * To create a mock objects for some class T, create a new class using:
664 * 1044 *
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after
743 /** The [log] of calls made. Only used if [name] is null. */ 1123 /** The [log] of calls made. Only used if [name] is null. */
744 LogEntryList log; 1124 LogEntryList log;
745 1125
746 /** How to handle unknown method calls - swallow or throw. */ 1126 /** How to handle unknown method calls - swallow or throw. */
747 final bool _throwIfNoBehavior; 1127 final bool _throwIfNoBehavior;
748 1128
749 /** Whether to create an audit log or not. */ 1129 /** Whether to create an audit log or not. */
750 bool _logging; 1130 bool _logging;
751 1131
752 bool get logging() => _logging; 1132 bool get logging() => _logging;
753 bool set logging(bool value) { 1133 set logging(bool value) {
754 if (value && log == null) { 1134 if (value && log == null) {
755 log = new LogEntryList(); 1135 log = new LogEntryList();
756 } 1136 }
757 _logging = value; 1137 _logging = value;
758 } 1138 }
759 1139
760 /** 1140 /**
761 * Default constructor. Unknown method calls are allowed and logged, 1141 * Default constructor. Unknown method calls are allowed and logged,
762 * the mock has no name, and has its own log. 1142 * the mock has no name, and has its own log.
763 */ 1143 */
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
830 // Get the first response. 1210 // Get the first response.
831 Responder response = actions[0]; 1211 Responder response = actions[0];
832 // If it is exhausted, remove it from the list. 1212 // If it is exhausted, remove it from the list.
833 // Note that for endlessly repeating values, we started the count at 1213 // Note that for endlessly repeating values, we started the count at
834 // 0, so we get a potentially useful value here, which is the 1214 // 0, so we get a potentially useful value here, which is the
835 // (negation of) the number of times we returned the value. 1215 // (negation of) the number of times we returned the value.
836 if (--response.count == 0) { 1216 if (--response.count == 0) {
837 actions.removeRange(0, 1); 1217 actions.removeRange(0, 1);
838 } 1218 }
839 // Do the response. 1219 // Do the response.
840 _Action action = response.action; 1220 Action action = response.action;
841 var value = response.value; 1221 var value = response.value;
842 if (action == _Action.RETURN) { 1222 if (action == Action.RETURN) {
843 if (_logging) { 1223 if (_logging) {
844 log.add(new LogEntry(name, method, args, action, value)); 1224 log.add(new LogEntry(name, method, args, action, value));
845 } 1225 }
846 return value; 1226 return value;
847 } else if (action == _Action.THROW) { 1227 } else if (action == Action.THROW) {
848 if (_logging) { 1228 if (_logging) {
849 log.add(new LogEntry(name, method, args, action, value)); 1229 log.add(new LogEntry(name, method, args, action, value));
850 } 1230 }
851 throw value; 1231 throw value;
852 } else if (action == _Action.PROXY) { 1232 } else if (action == Action.PROXY) {
853 var rtn; 1233 var rtn;
854 switch (args.length) { 1234 switch (args.length) {
855 case 0: 1235 case 0:
856 rtn = value(); 1236 rtn = value();
857 break; 1237 break;
858 case 1: 1238 case 1:
859 rtn = value(args[0]); 1239 rtn = value(args[0]);
860 break; 1240 break;
861 case 2: 1241 case 2:
862 rtn = value(args[0], args[1]); 1242 rtn = value(args[0], args[1]);
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
906 // actions are exhausted. This is considered an error. 1286 // actions are exhausted. This is considered an error.
907 throw new Exception('No more actions for method ' 1287 throw new Exception('No more actions for method '
908 '${_qualifiedName(name, method)}.'); 1288 '${_qualifiedName(name, method)}.');
909 } else if (_throwIfNoBehavior) { 1289 } else if (_throwIfNoBehavior) {
910 throw new Exception('No behavior specified for method ' 1290 throw new Exception('No behavior specified for method '
911 '${_qualifiedName(name, method)}.'); 1291 '${_qualifiedName(name, method)}.');
912 } 1292 }
913 // Otherwise user hasn't specified behavior for this method; we don't throw 1293 // Otherwise user hasn't specified behavior for this method; we don't throw
914 // so we can underspecify. 1294 // so we can underspecify.
915 if (_logging) { 1295 if (_logging) {
916 log.add(new LogEntry(name, method, args, _Action.IGNORE)); 1296 log.add(new LogEntry(name, method, args, Action.IGNORE));
917 } 1297 }
918 } 1298 }
919 1299
920 /** [verifyZeroInteractions] returns true if no calls were made */ 1300 /** [verifyZeroInteractions] returns true if no calls were made */
921 bool verifyZeroInteractions() { 1301 bool verifyZeroInteractions() {
922 if (log == null) { 1302 if (log == null) {
923 // This means we created the mock with logging off and have never turned 1303 // This means we created the mock with logging off and have never turned
924 // it on, so it doesn't make sense to verify behavior on such a mock. 1304 // it on, so it doesn't make sense to verify behavior on such a mock.
925 throw new 1305 throw new
926 Exception("Can't verify behavior when logging was never enabled."); 1306 Exception("Can't verify behavior when logging was never enabled.");
927 } 1307 }
928 return log.logs.length == 0; 1308 return log.logs.length == 0;
929 } 1309 }
930 1310
931 /** 1311 /**
932 * [getLogs] extracts all calls from the call log that match the 1312 * [getLogs] extracts all calls from the call log that match the
933 * [logFilter] [CallMatcher], and returns the matching list of 1313 * [logFilter], and returns the matching list of [LogEntry]s. If
934 * [LogEntry]s. If [destructive] is false (the default) the matching 1314 * [destructive] is false (the default) the matching calls are left
935 * calls are left in the log, else they are removed. Removal allows 1315 * in the log, else they are removed. Removal allows us to verify a
936 * us to verify a set of interactions and then verify that there are 1316 * set of interactions and then verify that there are no other
937 * no other interactions left. [actionMatcher] can be used to further 1317 * interactions left. [actionMatcher] can be used to further
938 * restrict the returned logs based on the action the mock performed. 1318 * restrict the returned logs based on the action the mock performed.
1319 * [logFilter] can be a [CallMatcher] or a predicate function that
1320 * takes a [LogEntry] and returns a bool.
939 * 1321 *
940 * Typical usage: 1322 * Typical usage:
941 * 1323 *
942 * getLogs(callsTo(...)).verify(...); 1324 * getLogs(callsTo(...)).verify(...);
943 */ 1325 */
944 LogEntryList getLogs([CallMatcher logFilter, 1326 LogEntryList getLogs([CallMatcher logFilter,
945 Matcher actionMatcher, 1327 Matcher actionMatcher,
946 bool destructive = false]) { 1328 bool destructive = false]) {
947 if (log == null) { 1329 if (log == null) {
948 // This means we created the mock with logging off and have never turned 1330 // This means we created the mock with logging off and have never turned
949 // it on, so it doesn't make sense to get logs from such a mock. 1331 // it on, so it doesn't make sense to get logs from such a mock.
950 throw new 1332 throw new
951 Exception("Can't retrieve logs when logging was never enabled."); 1333 Exception("Can't retrieve logs when logging was never enabled.");
952 } else { 1334 } else {
953 return log.getMatches(name, logFilter, actionMatcher, destructive); 1335 return log.getMatches(name, logFilter, actionMatcher, destructive);
954 } 1336 }
955 } 1337 }
1338
1339 /**
1340 * Useful shorthand method that creates a [CallMatcher] from its arguments
1341 * and then calls [getLogs].
1342 */
1343 LogEntryList calls(method,
1344 [arg0 = _noArg,
1345 arg1 = _noArg,
1346 arg2 = _noArg,
1347 arg3 = _noArg,
1348 arg4 = _noArg,
1349 arg5 = _noArg,
1350 arg6 = _noArg,
1351 arg7 = _noArg,
1352 arg8 = _noArg,
1353 arg9 = _noArg]) =>
1354 getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4,
1355 arg5, arg6, arg7, arg8, arg9));
956 } 1356 }
OLDNEW
« no previous file with comments | « no previous file | tests/lib/unittest/unittest_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698