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

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

Issue 10692051: Some more changes that came out of writing an article on mocks. (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
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 final RETURN = 0; 44 final IGNORE = 0;
Jennifer Messerly 2012/06/29 23:24:09 might want to wrap these in the enum pattern
45 final THROW = 1; 45 final RETURN = 1;
Jennifer Messerly 2012/06/29 23:24:09 also the ones after the first one could use doc co
46 final PROXY = 2; 46 final THROW = 2;
47 final PROXY = 3;
47 48
48 /** 49 /**
49 * The behavior of a method call in the mock library is specified 50 * The behavior of a method call in the mock library is specified
50 * with [Responder]s. A [Responder] has a [value] to throw 51 * with [Responder]s. A [Responder] has a [value] to throw
51 * or return (depending on whether [isThrow] is true or not, respectively), 52 * or return (depending on whether [isThrow] is true or not, respectively),
52 * and can either be one-shot, multi-shot, or infinitely repeating, 53 * and can either be one-shot, multi-shot, or infinitely repeating,
53 * depending on the value of [count (1, greater than 1, or 0 respectively). 54 * depending on the value of [count (1, greater than 1, or 0 respectively).
54 */ 55 */
55 class Responder { 56 class Responder {
56 var value; 57 var value;
57 int action; 58 int action;
58 int count; 59 int count;
59 Responder(this.value, [this.count = 1, this.action = RETURN]); 60 Responder(this.value, [this.count = 1, this.action = RETURN]);
60 } 61 }
61 62
62 /** 63 /**
63 * A [CallMatcher] is a special matcher used to match method calls (i.e. 64 * A [CallMatcher] is a special matcher used to match method calls (i.e.
64 * a method name and set of arguments). It is not a [Matcher] like the 65 * a method name and set of arguments). It is not a [Matcher] like the
65 * unit test [Matcher], but instead represents a collection of [Matcher]s, 66 * unit test [Matcher], but instead represents a method name and a
66 * one per argument, that will be applied to the parameters to decide if 67 * collection of [Matcher]s, one per argument, that will be applied
67 * the method call is a match. 68 * to the parameters to decide if the method call is a match.
68 */ 69 */
69 class CallMatcher { 70 class CallMatcher {
70 String name; 71 String name;
71 List<Matcher> argMatchers; 72 List<Matcher> argMatchers;
72 73
73 CallMatcher(String method, [ 74 CallMatcher(String method, [
74 arg0 = _noArg, 75 arg0 = _noArg,
75 arg1 = _noArg, 76 arg1 = _noArg,
76 arg2 = _noArg, 77 arg2 = _noArg,
77 arg3 = _noArg, 78 arg3 = _noArg,
(...skipping 145 matching lines...) Expand 10 before | Expand all | Expand 10 after
223 return thenCall(value, 0); 224 return thenCall(value, 0);
224 } 225 }
225 226
226 /** Returns true if a method call matches the [Behavior]. */ 227 /** Returns true if a method call matches the [Behavior]. */
227 bool matches(name, args) => matcher.matches(name, args); 228 bool matches(name, args) => matcher.matches(name, args);
228 229
229 /** Returns the [matcher]'s representation. */ 230 /** Returns the [matcher]'s representation. */
230 String toString() => matcher.toString(); 231 String toString() => matcher.toString();
231 } 232 }
232 233
234 String pad2(int v) {
235 String s = '0$v';
236 return s.substring(s.length-2);
237 }
238
233 /** 239 /**
234 * Every call to a [Mock] object method is logged. The logs are 240 * Every call to a [Mock] object method is logged. The logs are
235 * kept in instances of [LogEntry]. 241 * kept in instances of [LogEntry].
236 */ 242 */
237 class LogEntry { 243 class LogEntry {
238 final String name; // The method name. 244 Date when; // The time of the event.
Jennifer Messerly 2012/06/29 23:24:09 can this be final, and set it in an initializer:
245 final String mockName; // The mock object name, if any.
246 final String methodName; // The method name.
239 final List args; // The parameters. 247 final List args; // The parameters.
240 final int action; // The behavior that resulted. 248 final int action; // The behavior that resulted.
241 final value; // The value that was returned (if no throw). 249 final value; // The value that was returned (if no throw).
242 250
243 const LogEntry(this.name, this.args, this.action, [this.value = null]); 251 LogEntry(this.mockName, this.methodName,
252 this.args, this.action, [this.value = null]) {
Jennifer Messerly 2012/06/29 23:24:09 nit: can write this as "[this.value]". The "= null
253 when = new Date.now();
254 }
255
256 String toString([Date baseTime = null]) {
257 Description d = new StringDescription();
258 if (baseTime == null) {
259 // Show absolute time.
260 d.add('${when.hour}:${pad2(when.minute)}:'
261 '${pad2(when.second)}.${when.millisecond}> ');
262 } else {
263 // Show relative time.
264 int delta = when.millisecondsSinceEpoch - baseTime.millisecondsSinceEpoch;
265 int secs = (delta / 1000).toInt();
Jennifer Messerly 2012/06/29 23:24:09 nit: delta ~/ 1000 also, shouldn't need .toInt, if
266 int msecs = (delta % 1000).toInt();
Jennifer Messerly 2012/06/29 23:24:09 likewise, this shouldn't need .toInt
267 d.add('$secs.$msecs> ');
268 }
269 d.add('${_qualifiedName(mockName, methodName)}(');
270 for (var i = 0; i < args.length; i++) {
271 if (i != 0) d.add(', ');
272 d.addDescriptionOf(args[i]);
273 }
274 d.add(') ${action == THROW ? "threw" : "returned"} ');
275 d.addDescriptionOf(value);
276 return d.toString();
277 }
244 } 278 }
245 279
280 /** Utility function for optionally qualified method names */
Jennifer Messerly 2012/06/29 23:24:09 nit: extra newline here
281
282 String _qualifiedName(String owner, String method) {
283 if (owner == null) {
284 return method;
285 } else {
286 return '$owner.$method';
287 }
288 }
246 /** 289 /**
247 * We do verification on a list of [LogEntry]s. To allow chaining 290 * We do verification on a list of [LogEntry]s. To allow chaining
248 * of calls to verify, we encapsulate such a list in the [LogEntryList] 291 * of calls to verify, we encapsulate such a list in the [LogEntryList]
249 * class. 292 * class.
250 */ 293 */
251 class LogEntryList { 294 class LogEntryList {
252 final String filter; 295 final String filter;
253 final List<LogEntry> logs; 296 List<LogEntry> logs;
254 const LogEntryList(this.logs, [this.filter = null]); 297
298 LogEntryList([this.filter = null]) {
299 logs = new List<LogEntry>();
300 }
255 301
256 /** Add a [LogEntry] to the log. */ 302 /** Add a [LogEntry] to the log. */
257 add(LogEntry entry) => logs.add(entry); 303 add(LogEntry entry) => logs.add(entry);
258 304
259 /** 305 /**
260 * Create a new [LogEntryList] consisting of [LogEntry]s from 306 * Create a new [LogEntryList] consisting of [LogEntry]s from
261 * this list that match the specified [logfilter]. If [destructive] 307 * this list that match the specified [mockName] and [logfilter].
308 * If [mockName] is null, all entries will be checked. If [destructive]
262 * is true, the log entries are removed from the original list. 309 * is true, the log entries are removed from the original list.
263 */ 310 */
264 LogEntryList getMatches(CallMatcher logfilter, bool destructive) { 311 LogEntryList getMatches(String mockName,
265 LogEntryList rtn = 312 CallMatcher logFilter,
266 new LogEntryList(new List<LogEntry>(), logfilter.toString()); 313 [Matcher actionMatcher = null,
314 bool destructive = false]) {
315 String filterName = _qualifiedName(mockName, logFilter.toString());
316 LogEntryList rtn = new LogEntryList(filterName);
267 for (var i = 0; i < logs.length; i++) { 317 for (var i = 0; i < logs.length; i++) {
268 LogEntry entry = logs[i]; 318 LogEntry entry = logs[i];
269 if (logfilter.matches(entry.name, entry.args)) { 319 if (mockName != null && mockName != entry.mockName) {
270 rtn.add(entry); 320 continue;
271 if (destructive) { 321 }
272 logs.removeRange(i--, 1); 322 if (logFilter.matches(entry.methodName, entry.args)) {
323 if (actionMatcher == null || actionMatcher.matches(entry)) {
324 rtn.add(entry);
325 if (destructive) {
326 logs.removeRange(i--, 1);
327 }
273 } 328 }
274 } 329 }
275 } 330 }
276 return rtn; 331 return rtn;
277 } 332 }
278 333
279 /** Apply a unit test [Matcher] to the [LogEntryList]. */ 334 /** Apply a unit test [Matcher] to the [LogEntryList]. */
280 LogEntryList verify(Matcher matcher) { 335 LogEntryList verify(Matcher matcher) {
281 if (_mockFailureHandler == null) { 336 if (_mockFailureHandler == null) {
282 _mockFailureHandler = 337 _mockFailureHandler =
283 new _MockFailureHandler(getOrCreateExpectFailureHandler()); 338 new _MockFailureHandler(getOrCreateExpectFailureHandler());
284 } 339 }
285 expect(logs, matcher, filter, _mockFailureHandler); 340 expect(logs, matcher, filter, _mockFailureHandler);
286 return this; 341 return this;
287 } 342 }
343
344 String toString([Date baseTime = null]) {
345 String s = '';
346 for (var e in logs) {
347 s = '$s${e.toString(baseTime)}\n';
348 }
349 return s;
350 }
288 } 351 }
289 352
290 /** 353 /**
291 * [_TimesMatcher]s are used to make assertions about the number of 354 * [_TimesMatcher]s are used to make assertions about the number of
292 * times a method was called. 355 * times a method was called.
293 */ 356 */
294 class _TimesMatcher extends BaseMatcher { 357 class _TimesMatcher extends BaseMatcher {
295 final int min, max; 358 final int min, max;
296 359
297 const _TimesMatcher(this.min, [this.max = -1]); 360 const _TimesMatcher(this.min, [this.max = -1]);
(...skipping 11 matching lines...) Expand all
309 } else { 372 } else {
310 description.add('between $min and $max'); 373 description.add('between $min and $max');
311 } 374 }
312 return description.add(' times'); 375 return description.add(' times');
313 } 376 }
314 377
315 Description describeMismatch(log, Description mismatchDescription) => 378 Description describeMismatch(log, Description mismatchDescription) =>
316 mismatchDescription.add('was called ${log.length} times'); 379 mismatchDescription.add('was called ${log.length} times');
317 } 380 }
318 381
319 /** [calledExactly] matches an exact number of calls. */ 382 /** [happenedExactly] matches an exact number of calls. */
Jennifer Messerly 2012/06/29 23:24:09 not sure you really need the link, since it's just
320 Matcher calledExactly(count) { 383 Matcher happenedExactly(count) {
321 return new _TimesMatcher(count, count); 384 return new _TimesMatcher(count, count);
322 } 385 }
323 386
324 /** [calledAtLeast] matches a minimum number of calls. */ 387 /** [happenedAtLeast] matches a minimum number of calls. */
325 Matcher calledAtLeast(count) { 388 Matcher happenedAtLeast(count) {
326 return new _TimesMatcher(count); 389 return new _TimesMatcher(count);
327 } 390 }
328 391
329 /** [calledAtMost] matches a maximum number of calls. */ 392 /** [happenedAtMost] matches a maximum number of calls. */
330 Matcher calledAtMost(count) { 393 Matcher happenedAtMost(count) {
331 return new _TimesMatcher(0, count); 394 return new _TimesMatcher(0, count);
332 } 395 }
333 396
334 /** [neverCalled] matches zero calls. */ 397 /** [neverHappened] matches zero calls. */
335 final Matcher neverCalled = const _TimesMatcher(0, 0); 398 final Matcher neverHappened = const _TimesMatcher(0, 0);
336 399
337 /** [calledOnce] matches exactly one call. */ 400 /** [happenedOnce] matches exactly one call. */
338 final Matcher calledOnce = const _TimesMatcher(1, 1); 401 final Matcher happenedOnce = const _TimesMatcher(1, 1);
339 402
340 /** [calledAtLeastOnce] matches one or more calls. */ 403 /** [happenedAtLeastOnce] matches one or more calls. */
341 final Matcher calledAtLeastOnce = const _TimesMatcher(1); 404 final Matcher happenedAtLeastOnce = const _TimesMatcher(1);
342 405
343 /** [calledAtMostOnce] matches zero or one call. */ 406 /** [happenedAtMostOnce] matches zero or one call. */
344 final Matcher calledAtMostOnce = const _TimesMatcher(0, 1); 407 final Matcher happenedAtMostOnce = const _TimesMatcher(0, 1);
345 408
346 /** Special values for use with [_ResultMatcher] [frequency]. */
347 final int ALL = 0;
348 final int SOME = 1;
349 final int NONE = 2;
350 /** 409 /**
351 * [_ResultMatcher]s are used to make assertions about the results 410 * [_ResultMatcher]s are used to make assertions about the results
352 * of method calls. When filtering an execution log by calling 411 * of method calls. These can be used as optional parameters to getLogs().
353 * [forThe], a [LogEntrySet] of matching call logs is returned;
354 * [_ResultMatcher]s can then assert various things about this
355 * (sub)set of logs.
356 */ 412 */
357 class _ResultMatcher extends BaseMatcher { 413 class _ResultMatcher extends BaseMatcher {
358 final int action; 414 final int action;
359 final value; 415 final value;
416
417 const _ResultMatcher(this.action, this.value);
418
419 bool matches(item) {
420 if (item is! LogEntry) {
421 return false;
422 }
423 // normalize the action; PROXY is like RETURN.
424 int eaction = (item.action == THROW) ? THROW : RETURN;
425 return (eaction == action && value.matches(item.value));
426 }
427
428 Description describe(Description description) {
429 description.add(' to ');
430 if (action == RETURN || action == PROXY)
431 description.add('return ');
432 else
433 description.add('throw ');
434 return description.addDescriptionOf(value);
435 }
436
437 Description describeMismatch(log, Description mismatchDescription) {
438 if (entry.action == RETURN || entry.action == PROXY) {
439 mismatchDescription.add('returned ');
440 } else {
441 mismatchDescription.add('threw ');
442 }
443 mismatchDescription.add(entry.value);
444 return mismatchDescription;
445 }
446 }
447
448 /**
449 *[returning] matches log entries where the call to a method returned
450 * a value that matched [value].
451 */
452 Matcher returning(value) =>
453 new _ResultMatcher(RETURN, wrapMatcher(value));
454
455 /**
456 *[throwing] matches log entrues where the call to a method threw
457 * a value that matched [value].
458 */
459 Matcher throwing(value) =>
460 new _ResultMatcher(THROW, wrapMatcher(value));
461
462 /** Special values for use with [_ResultSetMatcher] [frequency]. */
Jennifer Messerly 2012/06/29 23:24:09 probably shouldn't link to an internal type. also,
463 final int ALL = 0;
464 final int SOME = 1;
465 final int NONE = 2;
466
467 /**
468 * [_ResultSetMatcher]s are used to make assertions about the results
469 * of method calls. When filtering an execution log by calling
470 * [getLogs], a [LogEntrySet] of matching call logs is returned;
471 * [_ResultSetMatcher]s can then assert various things about this
472 * (sub)set of logs.
473 *
474 * We could make this class use _ResultMatcher but it doesn't buy that
475 * match and adds some perf hit, so there is some duplication here.
476 */
477 class _ResultSetMatcher extends BaseMatcher {
478 final int action;
479 final value;
360 final int frequency; // -1 for all, 0 for none, 1 for some. 480 final int frequency; // -1 for all, 0 for none, 1 for some.
361 481
362 const _ResultMatcher(this.action, this.value, this.frequency); 482 const _ResultSetMatcher(this.action, this.value, this.frequency);
363 483
364 bool matches(log) { 484 bool matches(log) {
365 for (LogEntry entry in log) { 485 for (LogEntry entry in log) {
366 // normalize the action; PROXY is like RETURN. 486 // normalize the action; PROXY is like RETURN.
367 int eaction = (entry.action == THROW) ? THROW : RETURN; 487 int eaction = (entry.action == THROW) ? THROW : RETURN;
368 if (eaction == action && value.matches(entry.value)) { 488 if (eaction == action && value.matches(entry.value)) {
369 if (frequency == NONE) { 489 if (frequency == NONE) {
370 return false; 490 return false;
371 } else if (frequency == SOME) { 491 } else if (frequency == SOME) {
372 return true; 492 return true;
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
413 } 533 }
414 return mismatchDescription; 534 return mismatchDescription;
415 } 535 }
416 } 536 }
417 537
418 /** 538 /**
419 *[alwaysReturned] asserts that all matching calls to a method returned 539 *[alwaysReturned] asserts that all matching calls to a method returned
420 * a value that matched [value]. 540 * a value that matched [value].
421 */ 541 */
422 Matcher alwaysReturned(value) => 542 Matcher alwaysReturned(value) =>
423 new _ResultMatcher(RETURN, wrapMatcher(value), ALL); 543 new _ResultSetMatcher(RETURN, wrapMatcher(value), ALL);
424 544
425 /** 545 /**
426 *[sometimeReturned] asserts that at least one matching call to a method 546 *[sometimeReturned] asserts that at least one matching call to a method
427 * returned a value that matched [value]. 547 * returned a value that matched [value].
428 */ 548 */
429 Matcher sometimeReturned(value) => 549 Matcher sometimeReturned(value) =>
430 new _ResultMatcher(RETURN, wrapMatcher(value), SOME); 550 new _ResultSetMatcher(RETURN, wrapMatcher(value), SOME);
431 551
432 /** 552 /**
433 *[neverReturned] asserts that no matching calls to a method returned 553 *[neverReturned] asserts that no matching calls to a method returned
434 * a value that matched [value]. 554 * a value that matched [value].
435 */ 555 */
436 Matcher neverReturned(value) => 556 Matcher neverReturned(value) =>
437 new _ResultMatcher(RETURN, wrapMatcher(value), NONE); 557 new _ResultSetMatcher(RETURN, wrapMatcher(value), NONE);
438 558
439 /** 559 /**
440 *[alwaysThrew] asserts that all matching calls to a method threw 560 *[alwaysThrew] asserts that all matching calls to a method threw
441 * a value that matched [value]. 561 * a value that matched [value].
442 */ 562 */
443 Matcher alwaysThrew(value) => 563 Matcher alwaysThrew(value) =>
444 new _ResultMatcher(THROW, wrapMatcher(value), ALL); 564 new _ResultSetMatcher(THROW, wrapMatcher(value), ALL);
445 565
446 /** 566 /**
447 *[sometimeThrew] asserts that at least one matching call to a method threw 567 *[sometimeThrew] asserts that at least one matching call to a method threw
448 * a value that matched [value]. 568 * a value that matched [value].
449 */ 569 */
450 Matcher sometimeThrew(value) => 570 Matcher sometimeThrew(value) =>
451 new _ResultMatcher(THROW, wrapMatcher(value), SOME); 571 new _ResultSetMatcher(THROW, wrapMatcher(value), SOME);
452 572
453 /** 573 /**
454 *[neverThrew] asserts that no matching call to a method threw 574 *[neverThrew] asserts that no matching call to a method threw
455 * a value that matched [value]. 575 * a value that matched [value].
456 */ 576 */
457 Matcher neverThrew(value) => 577 Matcher neverThrew(value) =>
458 new _ResultMatcher(THROW, wrapMatcher(value), NONE); 578 new _ResultSetMatcher(THROW, wrapMatcher(value), NONE);
579
580 /** The shared log used for named mocks. */
581 LogEntryList sharedLog = null;
459 582
460 /** 583 /**
461 * [Mock] is the base class for all mocked objects, with 584 * [Mock] is the base class for all mocked objects, with
462 * support for basic mocking. 585 * support for basic mocking.
463 * 586 *
464 * To create a mock objects for some class T, create a new class using: 587 * To create a mock objects for some class T, create a new class using:
465 * 588 *
466 * class MockT extends Mock implements T {}; 589 * class MockT extends Mock implements T {};
467 * 590 *
468 * Then specify the behavior of the Mock for different methods using 591 * Then specify the behavior of the Mock for different methods using
469 * [when] (to select the method and parameters) and [thenReturn], 592 * [when] (to select the method and parameters) and [thenReturn],
470 * [alwaysReturn], [thenThrow], [alwaysThrow], [thenCall] or [alwaysCall]. 593 * [alwaysReturn], [thenThrow], [alwaysThrow], [thenCall] or [alwaysCall].
471 * [thenReturn], [thenThrow] and [thenCall] are one-shot so you would 594 * [thenReturn], [thenThrow] and [thenCall] are one-shot so you would
472 * typically call these more than once to specify a sequence of actions; 595 * typically call these more than once to specify a sequence of actions;
473 * this can be done with chained calls, e.g.: 596 * this can be done with chained calls, e.g.:
474 * 597 *
475 * m.when(callsTo('foo')). 598 * m.when(callsTo('foo')).
476 * thenReturn(0).thenReturn(1).thenReturn(2); 599 * thenReturn(0).thenReturn(1).thenReturn(2);
477 * 600 *
478 * [thenCall] and [alwaysCall] allow you to proxy mocked methods, chaining 601 * [thenCall] and [alwaysCall] allow you to proxy mocked methods, chaining
479 * to some other implementation. This provides a way to implement 'spies'. 602 * to some other implementation. This provides a way to implement 'spies'.
480 * 603 *
481 * You can then use the mock object. Once you are done, to verify the 604 * You can then use the mock object. Once you are done, to verify the
482 * behavior, use [forThe] to extract a relevant subset of method call 605 * behavior, use [getLogs] to extract a relevant subset of method call
483 * logs and apply [Matchers] to these through calling [verify]. 606 * logs and apply [Matchers] to these through calling [verify].
484 * 607 *
608 * A Mock can be given a name when constructed. In this case instead of
609 * keeping its own log, it uses a shared log. This can be useful to get an
610 * audit trail of interleaved behavior. It is the responsibility of the user
611 * to ensure that mock names, if used, are unique.
612 *
485 * Limitations: 613 * Limitations:
486 * - only positional parameters are supported (up to 10); 614 * - only positional parameters are supported (up to 10);
487 * - to mock getters you will need to include parentheses in the call 615 * - to mock getters you will need to include parentheses in the call
488 * (e.g. m.length() will work but not m.length). 616 * (e.g. m.length() will work but not m.length).
489 * 617 *
490 * Here is a simple example: 618 * Here is a simple example:
491 * 619 *
492 * class MockList extends Mock implements List {}; 620 * class MockList extends Mock implements List {};
493 * 621 *
494 * List m = new MockList(); 622 * List m = new MockList();
495 * m.when(callsTo('add', anything)).alwaysReturn(0); 623 * m.when(callsTo('add', anything)).alwaysReturn(0);
496 * 624 *
497 * m.add('foo'); 625 * m.add('foo');
498 * m.add('bar'); 626 * m.add('bar');
499 * 627 *
500 * getLogs(m, callsTo('add', anything)).verify(calledExactly(2)); 628 * getLogs(m, callsTo('add', anything)).verify(happenedExactly(2));
501 * getLogs(m, callsTo('add', 'foo')).verify(calledOnce); 629 * getLogs(m, callsTo('add', 'foo')).verify(happenedOnce);
502 * getLogs(m, callsTo('add', 'isNull)).verify(neverCalled); 630 * getLogs(m, callsTo('add', 'isNull)).verify(neverHappened);
503 * 631 *
504 * Note that we don't need to provide argument matchers for all arguments, 632 * Note that we don't need to provide argument matchers for all arguments,
505 * but we do need to provide arguments for all matchers. So this is allowed: 633 * but we do need to provide arguments for all matchers. So this is allowed:
506 * 634 *
507 * m.when(callsTo('add')).alwaysReturn(0); 635 * m.when(callsTo('add')).alwaysReturn(0);
508 * m.add(1, 2); 636 * m.add(1, 2);
509 * 637 *
510 * But this is not allowed and will throw an exception: 638 * But this is not allowed and will throw an exception:
511 * 639 *
512 * m.when(callsTo('add', anything, anything)).alwaysReturn(0); 640 * m.when(callsTo('add', anything, anything)).alwaysReturn(0);
(...skipping 10 matching lines...) Expand all
523 * class MockFoo extends Mock implements Foo { 651 * class MockFoo extends Mock implements Foo {
524 * Foo real; 652 * Foo real;
525 * MockFoo() { 653 * MockFoo() {
526 * real = new Foo(); 654 * real = new Foo();
527 * this.when(callsTo('bar')).alwaysCall(real.bar); 655 * this.when(callsTo('bar')).alwaysCall(real.bar);
528 * } 656 * }
529 * } 657 * }
530 * 658 *
531 */ 659 */
532 class Mock { 660 class Mock {
661 String name;
533 Map<String,Behavior> behaviors; /** The set of [behavior]s supported. */ 662 Map<String,Behavior> behaviors; /** The set of [behavior]s supported. */
534 LogEntryList log; /** The [log] of calls made. */ 663 LogEntryList log; /** The [log] of calls made. Only used if [name] is null. */
664 bool throwIfNoBehavior; /** If false, swallow unknown method calls. */
535 665
536 Mock() { 666 Mock([this.name = null, this.throwIfNoBehavior = false, this.log = null]) {
667 if (log == null) {
668 log = new LogEntryList();
669 }
537 behaviors = new Map<String,Behavior>(); 670 behaviors = new Map<String,Behavior>();
538 log = new LogEntryList(new List<LogEntry>());
539 } 671 }
540 672
541 /** 673 /**
542 * [when] is used to create a new or extend an existing [Behavior]. 674 * [when] is used to create a new or extend an existing [Behavior].
543 * A [CallMatcher] [filter] must be supplied, and the [Behavior]s for 675 * A [CallMatcher] [filter] must be supplied, and the [Behavior]s for
544 * that signature are returned (being created first if needed). 676 * that signature are returned (being created first if needed).
545 * 677 *
546 * Typical use case: 678 * Typical use case:
547 * 679 *
548 * mock.when(callsTo(...)).alwaysReturn(...); 680 * mock.when(callsTo(...)).alwaysReturn(...);
549 */ 681 */
550 Behavior when(CallMatcher logFilter) { 682 Behavior when(CallMatcher logFilter) {
551 String key = logFilter.toString(); 683 String key = logFilter.toString();
552 if (!behaviors.containsKey(key)) { 684 if (!behaviors.containsKey(key)) {
553 Behavior b = new Behavior(logFilter); 685 Behavior b = new Behavior(logFilter);
554 behaviors[key] = b; 686 behaviors[key] = b;
555 return b; 687 return b;
556 } else { 688 } else {
557 return behaviors[key]; 689 return behaviors[key];
558 } 690 }
559 } 691 }
560 692
561 /** 693 /**
562 * This is the handler for method calls. We loo through the list 694 * This is the handler for method calls. We loo through the list
563 * of [Behavior]s, and find the first match that still has return 695 * of [Behavior]s, and find the first match that still has return
564 * values available, and then do the action specified by that 696 * values available, and then do the action specified by that
565 * return value. If we find no [Behavior] to apply an exception is 697 * return value. If we find no [Behavior] to apply an exception is
566 * thrown. 698 * thrown.
567 */ 699 */
568 noSuchMethod(String name, List args) { 700 noSuchMethod(String method, List args) {
701 bool matchedMethodName = false;
569 for (String k in behaviors.getKeys()) { 702 for (String k in behaviors.getKeys()) {
570 Behavior b = behaviors[k]; 703 Behavior b = behaviors[k];
571 if (b.matches(name, args)) { 704 if (b.matcher.name == method) {
705 matchedMethodName = true;
706 }
707 if (b.matches(method, args)) {
572 List actions = b.actions; 708 List actions = b.actions;
573 if (actions == null || actions.length == 0) { 709 if (actions == null || actions.length == 0) {
574 continue; // No return values left in this Behavior. 710 continue; // No return values left in this Behavior.
575 } 711 }
576 // Get the first response. 712 // Get the first response.
577 Responder response = actions[0]; 713 Responder response = actions[0];
578 // If it is exhausted, remove it from the list. 714 // If it is exhausted, remove it from the list.
579 // Note that for endlessly repeating values, we started the count at 715 // Note that for endlessly repeating values, we started the count at
580 // 0, so we get a potentially useful value here, which is the 716 // 0, so we get a potentially useful value here, which is the
581 // (negation of) the number of times we returned the value. 717 // (negation of) the number of times we returned the value.
582 if (--response.count == 0) { 718 if (--response.count == 0) {
583 actions.removeRange(0, 1); 719 actions.removeRange(0, 1);
584 if (actions.length == 0) {
585 // Remove the behavior. Note that in the future there
586 // may be some value in preserving the behaviors for
587 // auditing purposes (e.g. how many times was this behavior used?).
588 // If we do decide to keep them and perf is an issue instead of
589 // deleting we could move this to a separate list.
590 behaviors.remove(k);
591 }
592 } 720 }
593 // Do the response. 721 // Do the response.
594 var action = response.action; 722 var action = response.action;
595 var value = response.value; 723 var value = response.value;
596 switch (action) { 724 switch (action) {
597 case RETURN: 725 case RETURN:
598 log.add(new LogEntry(name, args, action, value)); 726 log.add(new LogEntry(name, method, args, action, value));
599 return value; 727 return value;
600 case THROW: 728 case THROW:
601 log.add(new LogEntry(name, args, action, value)); 729 log.add(new LogEntry(name, method, args, action, value));
602 throw value; 730 throw value;
603 case PROXY: 731 case PROXY:
604 var rtn; 732 var rtn;
605 switch (args.length) { 733 switch (args.length) {
606 case 0: 734 case 0:
607 rtn = value(); 735 rtn = value();
608 break; 736 break;
609 case 1: 737 case 1:
610 rtn = value(args[0]); 738 rtn = value(args[0]);
611 break; 739 break;
(...skipping 26 matching lines...) Expand all
638 args[4], args[5], args[6], args[7], args[8]); 766 args[4], args[5], args[6], args[7], args[8]);
639 break; 767 break;
640 case 9: 768 case 9:
641 rtn = value(args[0], args[1], args[2], args[3], 769 rtn = value(args[0], args[1], args[2], args[3],
642 args[4], args[5], args[6], args[7], args[8], args[9]); 770 args[4], args[5], args[6], args[7], args[8], args[9]);
643 break; 771 break;
644 default: 772 default:
645 throw new Exception( 773 throw new Exception(
646 "Cannot proxy calls with more than 10 parameters"); 774 "Cannot proxy calls with more than 10 parameters");
647 } 775 }
648 log.add(new LogEntry(name, args, action, rtn)); 776 log.add(new LogEntry(name, method, args, action, rtn));
649 return rtn; 777 return rtn;
650 } 778 }
651 } 779 }
652 } 780 }
653 throw new Exception('No behavior specified for method $name'); 781 if (matchedMethodName) {
782 // User did specify behavior for this method, but all the
783 // actions are exhausted. This is considered an error.
784 throw new Exception('No more actions for method '
Jennifer Messerly 2012/06/29 23:24:09 is there a more subtype of Exception we could thro
785 '${_qualifiedName(name, method)}');
786 } else if (throwIfNoBehavior) {
787 throw new Exception('No behavior specified for method '
788 '${_qualifiedName(name, method)}');
789 }
790 // User hasn't specified behavior for this method; we don't throw
791 // so we can underspecify.
792 log.add(new LogEntry(name, method, args, IGNORE));
654 } 793 }
655 794
656 /** [verifyZeroInteractions] returns true if no calls were made */ 795 /** [verifyZeroInteractions] returns true if no calls were made */
657 bool verifyZeroInteractions() => log.logs.length == 0; 796 bool verifyZeroInteractions() => log.logs.length == 0;
658 }
659 797
660 /** 798 /**
661 * [getLogs] extracts all calls from the call log of [mock] that match the 799 * [getLogs] extracts all calls from the call log that match the
662 * [logFilter] [CallMatcher], and returns the matching list of 800 * [logFilter] [CallMatcher], and returns the matching list of
663 * [LogEntry]s. If [destructive] is false (the default) the matching 801 * [LogEntry]s. If [destructive] is false (the default) the matching
664 * calls are left in the mock object's log, else they are removed. 802 * calls are left in the log, else they are removed. Removal allows
665 * Removal allows us to verify a set of interactions and then verify 803 * us to verify a set of interactions and then verify that there are
666 * that there are no other interactions left. 804 * no other interactions left. [actionMatcher] can be used to further
667 * 805 * restrict the returned logs based on the action the mock performed.
668 * Typical usage: 806 *
669 * 807 * Typical usage:
670 * getLogs(mock, callsTo(...)).verify(...); 808 *
671 */ 809 * getLogs(callsTo(...)).verify(...);
672 LogEntryList getLogs(Mock mock, CallMatcher logFilter, 810 */
673 [bool destructive = false]) { 811 LogEntryList getLogs(CallMatcher logFilter, [Matcher actionMatcher = null,
674 return mock.log.getMatches(logFilter, destructive); 812 bool destructive = false]) {
813 return log.getMatches(name, logFilter, actionMatcher, destructive);
814 }
675 } 815 }
676 816
677 817
818
819
OLDNEW
« no previous file with comments | « no previous file | lib/unittest/operator_matchers.dart » ('j') | tests/lib/unittest/unittest_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698