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

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

Issue 10718002: Changed the syntax some. All the handling of variable argument lists is (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 22 matching lines...) Expand all
33 } 33 }
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. */
44 final RETURN = 0;
45 final THROW = 1;
46 final PROXY = 2;
47
43 /** 48 /**
44 * 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
45 * with [BehaviorValue]s. A [BehaviorValue] has a [value] to throw 50 * with [Responder]s. A [Responder] has a [value] to throw
46 * or return (depending on whether [isThrow] is true or not, respectively), 51 * or return (depending on whether [isThrow] is true or not, respectively),
47 * and can either be one-shot, multi-shot, or infinitely repeating, 52 * and can either be one-shot, multi-shot, or infinitely repeating,
48 * 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).
49 */ 54 */
50 class BehaviorValue { 55 class Responder {
51 var value; 56 var value;
52 bool isThrow; 57 int action;
53 int count; 58 int count;
54 BehaviorValue(this.value, [this.count = 1, this.isThrow = false]); 59 Responder(this.value, [this.count = 1, this.action = RETURN]);
55 } 60 }
56 61
57 /** 62 /**
58 * 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.
59 * 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
60 * unit test [Matcher], but instead represents a collection of [Matcher]s, 65 * unit test [Matcher], but instead represents a collection of [Matcher]s,
61 * one per argument, that will be applied to the parameters to decide if 66 * one per argument, that will be applied to the parameters to decide if
62 * the method call is a match. 67 * the method call is a match.
63 */ 68 */
64 class CallMatcher { 69 class CallMatcher {
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
115 } 120 }
116 d.add(')'); 121 d.add(')');
117 return d.toString(); 122 return d.toString();
118 } 123 }
119 124
120 /** 125 /**
121 * Given a [method] name oand list of [arguments], return true 126 * Given a [method] name oand list of [arguments], return true
122 * if it matches this [CallMatcher. 127 * if it matches this [CallMatcher.
123 */ 128 */
124 bool matches(String method, List arguments) { 129 bool matches(String method, List arguments) {
125 if (method != this.name || arguments.length != argMatchers.length) { 130 if (method != this.name) {
126 return false; 131 return false;
127 } 132 }
128 for (var i = 0; i < arguments.length; i++) { 133 if (arguments.length < argMatchers.length) {
134 throw new Exception("Less arguments than matchers for $name");
135 }
136 for (var i = 0; i < argMatchers.length; i++) {
129 if (!argMatchers[i].matches(arguments[i])) { 137 if (!argMatchers[i].matches(arguments[i])) {
130 return false; 138 return false;
131 } 139 }
132 } 140 }
133 return true; 141 return true;
134 } 142 }
135 } 143 }
136 144
145 /** [callsTo] returns a CallMatcher for the specified signature. */
146 CallMatcher callsTo(String method, [ arg0 = _noArg,
147 arg1 = _noArg,
148 arg2 = _noArg,
149 arg3 = _noArg,
150 arg4 = _noArg,
151 arg5 = _noArg,
152 arg6 = _noArg,
153 arg7 = _noArg,
154 arg8 = _noArg,
155 arg9 = _noArg]) {
156 return new CallMatcher(method, arg0, arg1, arg2, arg3, arg4,
157 arg5, arg6, arg7, arg8, arg9);
158 }
159
137 /** 160 /**
138 * A [Behavior] represents how a [Mock] will respond to one particular 161 * A [Behavior] represents how a [Mock] will respond to one particular
139 * type of method call. 162 * type of method call.
140 */ 163 */
141 class Behavior { 164 class Behavior {
142 CallMatcher matcher; // The method call matcher. 165 CallMatcher matcher; // The method call matcher.
143 List<BehaviorValue> returnValues; // The values to return/throw. 166 List<Responder> actions; // The values to return/throw or proxies to call.
144 167
145 Behavior (this.matcher) { 168 Behavior (this.matcher) {
146 returnValues = new List<BehaviorValue>(); 169 actions = new List<Responder>();
147 } 170 }
148 171
149 /** 172 /**
150 * [thenReturn] creates a return value, that is returned [count] 173 * Adds a [Responder] that returns a [value] for [count] calls
151 * times (1 by default). 174 * (1 by default).
152 */ 175 */
153 Behavior thenReturn(value, [count = 1]) { 176 Behavior thenReturn(value, [count = 1]) {
154 returnValues.add(new BehaviorValue(value, count)); 177 actions.add(new Responder(value, count, RETURN));
155 return this; // For chaining calls. 178 return this; // For chaining calls.
156 } 179 }
157 180
158 /** [alwaysReturn] creates a repeating return value. */ 181 /** Adds a [Responder] that repeatedly returns a [value]. */
159 Behavior alwaysReturn(value) { 182 Behavior alwaysReturn(value) {
160 return thenReturn(value, 0); 183 return thenReturn(value, 0);
161 } 184 }
162 185
163 /** 186 /**
164 * [thenThrow] creates an exception, that is thrown [count] 187 * Adds a [Responder] that throws [value] [count]
165 * times (1 by default). 188 * times (1 by default).
166 */ 189 */
167 Behavior thenThrow(value, [count = 1]) { 190 Behavior thenThrow(value, [count = 1]) {
168 returnValues.add(new BehaviorValue(value, count, true)); 191 actions.add(new Responder(value, count, THROW));
169 return this; // For chaining calls. 192 return this; // For chaining calls.
170 } 193 }
171 194
172 /** [alwaysThrow] creates a repeating exception. */ 195 /** Adds a [Responder] that throws [value] endlessly. */
173 Behavior alwaysThrow(value) { 196 Behavior alwaysThrow(value) {
174 return thenThrow(value, 0); 197 return thenThrow(value, 0);
175 } 198 }
176 199
177 /** [matches] return true if a method call matches the [Behavior]. */ 200 /**
201 * [thenCall] creates a proxy Responder, that is called [count]
202 * times (1 by default; 0 is used for unlimited calls, and is
203 * exposed as [alwaysCall]). [value] is the function that will
204 * be called with the same arguments that were passed to the
205 * mock. Proxies can be used to wrap real objects or to define
206 * more complex return/throw behavior. You could even (if you
207 * wanted) use proxies to emulate the behavior of thenReturn;
208 * e.g.:
209 *
210 * m.when(callsTo('foo')).thenReturn(0)
211 *
212 * is equivalent to:
213 *
214 * m.when(callsTo('foo')).thenCall(() => 0)
215 */
216 Behavior thenCall(value, [count = 1]) {
217 actions.add(new Responder(value, count, PROXY));
218 return this; // For chaining calls.
219 }
220
221 /** Creates a repeating proxy call. */
222 Behavior alwaysCall(value) {
223 return thenCall(value, 0);
224 }
225
226 /** Returns true if a method call matches the [Behavior]. */
178 bool matches(name, args) => matcher.matches(name, args); 227 bool matches(name, args) => matcher.matches(name, args);
228
229 /** Returns the [matcher]'s representation. */
230 String toString() => matcher.toString();
179 } 231 }
180 232
181 /** 233 /**
182 * 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
183 * kept in instances of [LogEntry]. 235 * kept in instances of [LogEntry].
184 */ 236 */
185 class LogEntry { 237 class LogEntry {
186 final String name; // The method name. 238 final String name; // The method name.
187 final List args; // The parameters. 239 final List args; // The parameters.
188 final BehaviorValue result; // The behavior that resulted. 240 final int action; // The behavior that resulted.
241 final value; // The value that was returned (if no throw).
189 242
190 const LogEntry(this.name, this.args, this.result); 243 const LogEntry(this.name, this.args, this.action, [this.value = null]);
191 } 244 }
192 245
193 /** 246 /**
194 * 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
195 * 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]
196 * class. 249 * class.
197 */ 250 */
198 class LogEntryList { 251 class LogEntryList {
199 final String filter; 252 final String filter;
200 final List<LogEntry> logs; 253 final List<LogEntry> logs;
201 const LogEntryList(this.logs, [this.filter = null]); 254 const LogEntryList(this.logs, [this.filter = null]);
202 255
203 /** Add a [LogEntry] to the log. */ 256 /** Add a [LogEntry] to the log. */
204 add(LogEntry entry) => logs.add(entry); 257 add(LogEntry entry) => logs.add(entry);
205 258
206 /** 259 /**
207 * Create a new [LogEntryList] consisting of [LogEntry]s from 260 * Create a new [LogEntryList] consisting of [LogEntry]s from
208 * this list that match the specified [logfilter]. 261 * this list that match the specified [logfilter]. If [destructive]
262 * is true, the log entries are removed from the original list.
209 */ 263 */
210 LogEntryList getMatches(CallMatcher logfilter) { 264 LogEntryList getMatches(CallMatcher logfilter, bool destructive) {
211 LogEntryList rtn = 265 LogEntryList rtn =
212 new LogEntryList(new List<LogEntry>(), logfilter.toString()); 266 new LogEntryList(new List<LogEntry>(), logfilter.toString());
213 for (var i = 0; i < logs.length; i++) { 267 for (var i = 0; i < logs.length; i++) {
214 LogEntry entry = logs[i]; 268 LogEntry entry = logs[i];
215 if (logfilter.matches(entry.name, entry.args)) { 269 if (logfilter.matches(entry.name, entry.args)) {
216 rtn.add(entry); 270 rtn.add(entry);
271 if (destructive) {
272 logs.removeRange(i--, 1);
273 }
217 } 274 }
218 } 275 }
219 return rtn; 276 return rtn;
220 } 277 }
221 278
222 /** Apply a unit test [Matcher] to the [LogEntryList]. */ 279 /** Apply a unit test [Matcher] to the [LogEntryList]. */
223 LogEntryList verify(Matcher matcher) { 280 LogEntryList verify(Matcher matcher) {
224 if (_mockFailureHandler == null) { 281 if (_mockFailureHandler == null) {
225 _mockFailureHandler = 282 _mockFailureHandler =
226 new _MockFailureHandler(getOrCreateExpectFailureHandler()); 283 new _MockFailureHandler(getOrCreateExpectFailureHandler());
227 } 284 }
228 expect(logs, matcher, filter, _mockFailureHandler); 285 expect(logs, matcher, filter, _mockFailureHandler);
229 return this; 286 return this;
230 } 287 }
231 } 288 }
232 289
233 /** 290 /**
234 * [_TimesMatcher]s are used to make assertions about the number of 291 * [_TimesMatcher]s are used to make assertions about the number of
235 * times a method was called. 292 * times a method was called.
236 */ 293 */
237 class _TimesMatcher extends BaseMatcher { 294 class _TimesMatcher extends BaseMatcher {
238 final int min, max; 295 final int min, max;
296
239 const _TimesMatcher(this.min, [this.max = -1]); 297 const _TimesMatcher(this.min, [this.max = -1]);
298
240 bool matches(log) => log.length >= min && (max < 0 || log.length <= max); 299 bool matches(log) => log.length >= min && (max < 0 || log.length <= max);
300
241 Description describe(Description description) { 301 Description describe(Description description) {
242 description.add(' to be called '); 302 description.add(' to be called ');
243 if (max < 0) { 303 if (max < 0) {
244 description.add('at least $min'); 304 description.add('at least $min');
245 } else if (max == min) { 305 } else if (max == min) {
246 description.add('$max'); 306 description.add('$max');
247 } else if (min == 0) { 307 } else if (min == 0) {
248 description.add('at most $max'); 308 description.add('at most $max');
249 } else { 309 } else {
250 description.add('between $min and $max'); 310 description.add('between $min and $max');
251 } 311 }
252 return description.add(' times'); 312 return description.add(' times');
253 } 313 }
314
254 Description describeMismatch(log, Description mismatchDescription) => 315 Description describeMismatch(log, Description mismatchDescription) =>
255 mismatchDescription.add('was called ${log.length} times'); 316 mismatchDescription.add('was called ${log.length} times');
256 } 317 }
257 318
258 /** [calledExactly] matches an exact number of calls. */ 319 /** [calledExactly] matches an exact number of calls. */
259 Matcher calledExactly(count) { 320 Matcher calledExactly(count) {
260 return new _TimesMatcher(count, count); 321 return new _TimesMatcher(count, count);
261 } 322 }
262 323
263 /** [calledAtLeast] matches a minimum number of calls. */ 324 /** [calledAtLeast] matches a minimum number of calls. */
(...skipping 11 matching lines...) Expand all
275 336
276 /** [calledOnce] matches exactly one call. */ 337 /** [calledOnce] matches exactly one call. */
277 final Matcher calledOnce = const _TimesMatcher(1, 1); 338 final Matcher calledOnce = const _TimesMatcher(1, 1);
278 339
279 /** [calledAtLeastOnce] matches one or more calls. */ 340 /** [calledAtLeastOnce] matches one or more calls. */
280 final Matcher calledAtLeastOnce = const _TimesMatcher(1); 341 final Matcher calledAtLeastOnce = const _TimesMatcher(1);
281 342
282 /** [calledAtMostOnce] matches zero or one call. */ 343 /** [calledAtMostOnce] matches zero or one call. */
283 final Matcher calledAtMostOnce = const _TimesMatcher(0, 1); 344 final Matcher calledAtMostOnce = const _TimesMatcher(0, 1);
284 345
346 /** Special values for use with [_ResultMatcher] [frequency]. */
347 final int ALL = 0;
348 final int SOME = 1;
349 final int NONE = 2;
350 /**
351 * [_ResultMatcher]s are used to make assertions about the results
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.
356 */
357 class _ResultMatcher extends BaseMatcher {
358 final int action;
359 final value;
360 final int frequency; // -1 for all, 0 for none, 1 for some.
361
362 const _ResultMatcher(this.action, this.value, this.frequency);
363
364 bool matches(log) {
365 for (LogEntry entry in log) {
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 }
380 }
381 // If we get here, then if count is ALL we got all matches and
382 // this is success; otherwise we got all mismatched which is
383 // success for count == NONE and failure for count == SOME.
384 return (frequency != SOME);
385 }
386
387 Description describe(Description description) {
388 description.add(' to ');
389 description.add(frequency == ALL ? 'alway ' :
390 (frequency == NONE ? 'never ' : 'sometimes '));
391 if (action == RETURN || action == PROXY)
392 description.add('return ');
393 else
394 description.add('throw ');
395 return description.addDescriptionOf(value);
396 }
397
398 Description describeMismatch(log, Description mismatchDescription) {
399 if (frequency != SOME) {
400 for (LogEntry entry in log) {
401 if (entry.action != action || !value.matches(entry.value)) {
402 if (entry.action == RETURN || entry.action == PROXY)
403 mismatchDescription.add('returned ');
404 else
405 mismatchDescription.add('threw ');
406 mismatchDescription.add(entry.value);
407 mismatchDescription.add(' at least once');
408 break;
409 }
410 }
411 } else {
412 mismatchDescription.add('never did');
413 }
414 return mismatchDescription;
415 }
416 }
417
418 /**
419 *[alwaysReturned] asserts that all matching calls to a method returned
420 * a value that matched [value].
421 */
422 Matcher alwaysReturned(value) =>
423 new _ResultMatcher(RETURN, wrapMatcher(value), ALL);
424
425 /**
426 *[sometimeReturned] asserts that at least one matching call to a method
427 * returned a value that matched [value].
428 */
429 Matcher sometimeReturned(value) =>
430 new _ResultMatcher(RETURN, wrapMatcher(value), SOME);
431
432 /**
433 *[neverReturned] asserts that no matching calls to a method returned
434 * a value that matched [value].
435 */
436 Matcher neverReturned(value) =>
437 new _ResultMatcher(RETURN, wrapMatcher(value), NONE);
438
439 /**
440 *[alwaysThrew] asserts that all matching calls to a method threw
441 * a value that matched [value].
442 */
443 Matcher alwaysThrew(value) =>
444 new _ResultMatcher(THROW, wrapMatcher(value), ALL);
445
446 /**
447 *[sometimeThrew] asserts that at least one matching call to a method threw
448 * a value that matched [value].
449 */
450 Matcher sometimeThrew(value) =>
451 new _ResultMatcher(THROW, wrapMatcher(value), SOME);
452
453 /**
454 *[neverThrew] asserts that no matching call to a method threw
455 * a value that matched [value].
456 */
457 Matcher neverThrew(value) =>
458 new _ResultMatcher(THROW, wrapMatcher(value), NONE);
459
285 /** 460 /**
286 * [Mock] is the base class for all mocked objects, with 461 * [Mock] is the base class for all mocked objects, with
287 * support for basic mocking. 462 * support for basic mocking.
288 * 463 *
289 * 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:
290 * 465 *
291 * class MockT extends Mock implements T {}; 466 * class MockT extends Mock implements T {};
292 * 467 *
293 * Then specify the behavior of the Mock for different methods using 468 * Then specify the behavior of the Mock for different methods using
294 * [when] (to select the method and parameters) and [thenReturn], 469 * [when] (to select the method and parameters) and [thenReturn],
295 * [alwaysReturn], [thenThrow] and/or [alwaysThrow]. 470 * [alwaysReturn], [thenThrow], [alwaysThrow], [thenCall] or [alwaysCall].
471 * [thenReturn], [thenThrow] and [thenCall] are one-shot so you would
472 * typically call these more than once to specify a sequence of actions;
473 * this can be done with chained calls, e.g.:
474 *
475 * m.when(callsTo('foo')).
476 * thenReturn(0).thenReturn(1).thenReturn(2);
477 *
478 * [thenCall] and [alwaysCall] allow you to proxy mocked methods, chaining
479 * to some other implementation. This provides a way to implement 'spies'.
296 * 480 *
297 * 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
298 * behavior, use [verify] to extract a relevant subset of method call 482 * behavior, use [forThe] to extract a relevant subset of method call
299 * logs and apply [Matchers] to these. 483 * logs and apply [Matchers] to these through calling [verify].
300 * 484 *
301 * Limitations: 485 * Limitations:
302 * - only positional parameters are supported (up to 10); 486 * - only positional parameters are supported (up to 10);
303 * - to mock getters you will need to include parentheses. 487 * - to mock getters you will need to include parentheses in the call
488 * (e.g. m.length() will work but not m.length).
304 * 489 *
305 * Here is a simple example: 490 * Here is a simple example:
306 * 491 *
307 * class MockList extends Mock implements List {}; 492 * class MockList extends Mock implements List {};
308 * 493 *
309 * List m = new MockList(); 494 * List m = new MockList();
310 * m.when('add', anything).alwaysReturn(0); 495 * m.when(callsTo('add', anything)).alwaysReturn(0);
311 * 496 *
312 * m.add('foo'); 497 * m.add('foo');
313 * m.add('bar'); 498 * m.add('bar');
314 * 499 *
315 * m.verify('add', anything, was:calledExactly(2)); 500 * getLogs(m, callsTo('add', anything)).verify(calledExactly(2));
316 * m.verify('add', 'foo', was:calledOnce); 501 * getLogs(m, callsTo('add', 'foo')).verify(calledOnce);
317 * m.verify('add', 'isNull, was:neverCalled); 502 * getLogs(m, callsTo('add', 'isNull)).verify(neverCalled);
503 *
504 * 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:
506 *
507 * m.when(callsTo('add')).alwaysReturn(0);
508 * m.add(1, 2);
509 *
510 * But this is not allowed and will throw an exception:
511 *
512 * m.when(callsTo('add', anything, anything)).alwaysReturn(0);
513 * m.add(1);
514 *
515 * Here is a way to implement a 'spy', which is where we log the call
516 * but then hand it off to some other function, which is the same
517 * method in a real instance of the class being mocked:
518 *
519 * class Foo {
520 * bar(a, b, c) => a + b + c;
521 * }
522 *
523 * class MockFoo extends Mock implements Foo {
524 * Foo real;
525 * MockFoo() {
526 * real = new Foo();
527 * this.when(callsTo('bar')).alwaysCall(real.bar);
528 * }
529 * }
530 *
318 */ 531 */
319 class Mock { 532 class Mock {
320 Map<String,Behavior> behaviors; /** The set of [behavior]s supported. */ 533 Map<String,Behavior> behaviors; /** The set of [behavior]s supported. */
321 LogEntryList log; /** The [log] of calls made. */ 534 LogEntryList log; /** The [log] of calls made. */
322 535
323 Mock() { 536 Mock() {
324 behaviors = new Map<String,Behavior>(); 537 behaviors = new Map<String,Behavior>();
325 log = new LogEntryList(new List<LogEntry>()); 538 log = new LogEntryList(new List<LogEntry>());
326 } 539 }
327 540
328 /** 541 /**
329 * [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].
330 * The [method] name and the argument [Matcher] is specified. A 543 * A [CallMatcher] [filter] must be supplied, and the [Behavior]s for
331 * corresponding [CallMatcher] is created, and the [Behavior]s for 544 * that signature are returned (being created first if needed).
332 * its signature are returned (being created first if needed). 545 *
546 * Typical use case:
547 *
548 * mock.when(callsTo(...)).alwaysReturn(...);
333 */ 549 */
334 Behavior when(String method, [ 550 Behavior when(CallMatcher logFilter) {
335 arg0 = _noArg, 551 String key = logFilter.toString();
336 arg1 = _noArg,
337 arg2 = _noArg,
338 arg3 = _noArg,
339 arg4 = _noArg,
340 arg5 = _noArg,
341 arg6 = _noArg,
342 arg7 = _noArg,
343 arg8 = _noArg,
344 arg9 = _noArg]) {
345 CallMatcher logfilter = new CallMatcher(method,
346 arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
347 String key = logfilter.toString();
348 if (!behaviors.containsKey(key)) { 552 if (!behaviors.containsKey(key)) {
349 Behavior b = new Behavior(logfilter); 553 Behavior b = new Behavior(logFilter);
350 behaviors[key] = b; 554 behaviors[key] = b;
351 return b; 555 return b;
352 } else { 556 } else {
353 return behaviors[key]; 557 return behaviors[key];
354 } 558 }
355 } 559 }
356 560
357 /** 561 /**
358 * 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
359 * 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
360 * values available, and then do the action specified by that 564 * values available, and then do the action specified by that
361 * 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
362 * thrown. 566 * thrown.
363 */ 567 */
364 noSuchMethod(String name, List args) { 568 noSuchMethod(String name, List args) {
365 for (String k in behaviors.getKeys()) { 569 for (String k in behaviors.getKeys()) {
366 Behavior b = behaviors[k]; 570 Behavior b = behaviors[k];
367 if (b.matches(name, args)) { 571 if (b.matches(name, args)) {
368 List rv = b.returnValues; 572 List actions = b.actions;
369 if (rv == null || rv.length == 0) { 573 if (actions == null || actions.length == 0) {
370 continue; // No return values left in this Behavior. 574 continue; // No return values left in this Behavior.
371 } 575 }
372 // Get the first response. 576 // Get the first response.
373 BehaviorValue bv = rv[0]; 577 Responder response = actions[0];
374 // If it is exhausted, remove it from the list. 578 // If it is exhausted, remove it from the list.
375 // Note that for endlessly repeating values, we started the count at 579 // Note that for endlessly repeating values, we started the count at
376 // 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
377 // (negation of) the number of times we returned the value. 581 // (negation of) the number of times we returned the value.
378 if (--bv.count == 0) { 582 if (--response.count == 0) {
379 rv.removeRange(0, 1); 583 actions.removeRange(0, 1);
380 if (rv.length == 0) { 584 if (actions.length == 0) {
381 // Remove the behavior. Note that in the future there 585 // Remove the behavior. Note that in the future there
382 // may be some value in preserving the behaviors for 586 // may be some value in preserving the behaviors for
383 // auditing purposes (e.g. how many times was this behavior used?). 587 // auditing purposes (e.g. how many times was this behavior used?).
384 // If we do decide to keep them and perf is an issue instead of 588 // If we do decide to keep them and perf is an issue instead of
385 // deleting we could move this to a separate list. 589 // deleting we could move this to a separate list.
386 behaviors.remove(k); 590 behaviors.remove(k);
387 } 591 }
388 } 592 }
389 // Log the method call and the response.
390 log.add(new LogEntry(name, args, bv));
391 // Do the response. 593 // Do the response.
392 if (bv.isThrow) { 594 var action = response.action;
393 throw bv.value; 595 var value = response.value;
394 } else { 596 switch (action) {
395 return bv.value; 597 case RETURN:
598 log.add(new LogEntry(name, args, action, value));
599 return value;
600 case THROW:
601 log.add(new LogEntry(name, args, action, value));
602 throw value;
603 case PROXY:
604 var rtn;
605 switch (args.length) {
606 case 0:
607 rtn = value();
608 break;
609 case 1:
610 rtn = value(args[0]);
611 break;
612 case 2:
613 rtn = value(args[0], args[1]);
614 break;
615 case 3:
616 rtn = value(args[0], args[1], args[2]);
617 break;
618 case 4:
619 rtn = value(args[0], args[1], args[2], args[3]);
620 break;
621 case 5:
622 rtn = value(args[0], args[1], args[2], args[3], args[4]);
623 break;
624 case 6:
625 rtn = value(args[0], args[1], args[2], args[3],
626 args[4], args[5]);
627 break;
628 case 7:
629 rtn = value(args[0], args[1], args[2], args[3],
630 args[4], args[5], args[6]);
631 break;
632 case 8:
633 rtn = value(args[0], args[1], args[2], args[3],
634 args[4], args[5], args[6], args[7]);
635 break;
636 case 9:
637 rtn = value(args[0], args[1], args[2], args[3],
638 args[4], args[5], args[6], args[7], args[8]);
639 break;
640 case 9:
641 rtn = value(args[0], args[1], args[2], args[3],
642 args[4], args[5], args[6], args[7], args[8], args[9]);
643 break;
644 default:
645 throw new Exception(
646 "Cannot proxy calls with more than 10 parameters");
647 }
648 log.add(new LogEntry(name, args, action, rtn));
649 return rtn;
396 } 650 }
397 } 651 }
398 } 652 }
399 throw new Exception('No behavior specified for method $name'); 653 throw new Exception('No behavior specified for method $name');
400 } 654 }
401 655
402 /**
403 * [verify] extracts all calls from the object log that match the
404 * method signature, then applies the [was] matcher. The matching
405 * list of [LogEntry]s is returned so that further calls to verify()
406 * can be chained .
407 */
408 LogEntryList verify(String method, [ arg0 = _noArg,
409 arg1 = _noArg,
410 arg2 = _noArg,
411 arg3 = _noArg,
412 arg4 = _noArg,
413 arg5 = _noArg,
414 arg6 = _noArg,
415 arg7 = _noArg,
416 arg8 = _noArg,
417 arg9 = _noArg,
418 Matcher was = calledOnce]) {
419 CallMatcher logfilter = new CallMatcher(method,
420 arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9);
421 LogEntryList _logs = log.getMatches(logfilter);
422 _logs.verify(was);
423 return _logs;
424 }
425
426 /** [verifyZeroInteractions] returns true if no calls were made */ 656 /** [verifyZeroInteractions] returns true if no calls were made */
427 bool verifyZeroInteractions() => log.logs.length == 0; 657 bool verifyZeroInteractions() => log.logs.length == 0;
428
429 } 658 }
430 659
660 /**
661 * [getLogs] extracts all calls from the call log of [mock] that match the
662 * [logFilter] [CallMatcher], and returns the matching list of
663 * [LogEntry]s. If [destructive] is false (the default) the matching
664 * calls are left in the mock object's log, else they are removed.
665 * Removal allows us to verify a set of interactions and then verify
666 * that there are no other interactions left.
667 *
668 * Typical usage:
669 *
670 * getLogs(mock, callsTo(...)).verify(...);
671 */
672 LogEntryList getLogs(Mock mock, CallMatcher logFilter,
673 [bool destructive = false]) {
674 return mock.log.getMatches(logFilter, destructive);
675 }
676
677
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