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

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

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

Powered by Google App Engine
This is Rietveld 408576698