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

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