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

Unified Diff: lib/unittest/mock.dart

Issue 10752007: Mocking library improvements: (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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | lib/unittest/operator_matchers.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: lib/unittest/mock.dart
===================================================================
--- lib/unittest/mock.dart (revision 9483)
+++ lib/unittest/mock.dart (working copy)
@@ -41,10 +41,24 @@
final _noArg = const _Sentinel();
/** The ways in which a call to a mock method can be handled. */
-final RETURN = 0;
-final THROW = 1;
-final PROXY = 2;
+class _Action {
+ /** Do nothing (void method) */
+ static final IGNORE = const _Action._('IGNORE');
+ /** Return a supplied value. */
+ static final RETURN = const _Action._('RETURN');
+
+ /** Throw a supplied value. */
+ static final THROW = const _Action._('THROW');
+
+ /** Call a supplied function. */
+ static final PROXY = const _Action._('PROXY');
+
+ const _Action._(this.name);
+
+ final String name;
+}
+
/**
* The behavior of a method call in the mock library is specified
* with [Responder]s. A [Responder] has a [value] to throw
@@ -54,23 +68,23 @@
*/
class Responder {
var value;
- int action;
+ _Action action;
int count;
- Responder(this.value, [this.count = 1, this.action = RETURN]);
+ Responder(this.value, [this.count = 1, this.action = _Action.RETURN]);
}
/**
* A [CallMatcher] is a special matcher used to match method calls (i.e.
* a method name and set of arguments). It is not a [Matcher] like the
- * unit test [Matcher], but instead represents a collection of [Matcher]s,
- * one per argument, that will be applied to the parameters to decide if
- * the method call is a match.
+ * unit test [Matcher], but instead represents a method name and a
+ * collection of [Matcher]s, one per argument, that will be applied
+ * to the parameters to decide if the method call is a match.
*/
class CallMatcher {
String name;
List<Matcher> argMatchers;
- CallMatcher(String method, [
+ CallMatcher(this.name, [
arg0 = _noArg,
arg1 = _noArg,
arg2 = _noArg,
@@ -81,7 +95,6 @@
arg7 = _noArg,
arg8 = _noArg,
arg9 = _noArg]) {
- name = method;
argMatchers = new List<Matcher>();
if (arg0 == _noArg) return;
argMatchers.add(wrapMatcher(arg0));
@@ -123,7 +136,7 @@
}
/**
- * Given a [method] name oand list of [arguments], return true
+ * Given a [method] name and list of [arguments], return true
* if it matches this [CallMatcher.
*/
bool matches(String method, List arguments) {
@@ -143,7 +156,8 @@
}
/** [callsTo] returns a CallMatcher for the specified signature. */
-CallMatcher callsTo(String method, [ arg0 = _noArg,
+CallMatcher callsTo(String method, [
+ arg0 = _noArg,
arg1 = _noArg,
arg2 = _noArg,
arg3 = _noArg,
@@ -174,7 +188,7 @@
* (1 by default).
*/
Behavior thenReturn(value, [count = 1]) {
- actions.add(new Responder(value, count, RETURN));
+ actions.add(new Responder(value, count, _Action.RETURN));
return this; // For chaining calls.
}
@@ -188,7 +202,7 @@
* times (1 by default).
*/
Behavior thenThrow(value, [count = 1]) {
- actions.add(new Responder(value, count, THROW));
+ actions.add(new Responder(value, count, _Action.THROW));
return this; // For chaining calls.
}
@@ -214,7 +228,7 @@
* m.when(callsTo('foo')).thenCall(() => 0)
*/
Behavior thenCall(value, [count = 1]) {
- actions.add(new Responder(value, count, PROXY));
+ actions.add(new Responder(value, count, _Action.PROXY));
return this; // For chaining calls.
}
@@ -224,7 +238,7 @@
}
/** Returns true if a method call matches the [Behavior]. */
- bool matches(name, args) => matcher.matches(name, args);
+ bool matches(String method, List args) => matcher.matches(method, args);
/** Returns the [matcher]'s representation. */
String toString() => matcher.toString();
@@ -235,14 +249,64 @@
* kept in instances of [LogEntry].
*/
class LogEntry {
- final String name; // The method name.
- final List args; // The parameters.
- final int action; // The behavior that resulted.
- final value; // The value that was returned (if no throw).
+ /** The time of the event. */
+ Date when;
Siggi Cherem (dart-lang) 2012/07/09 22:11:05 when => time? (otherwise it is confusing because o
gram 2012/07/09 22:35:29 Done.
- const LogEntry(this.name, this.args, this.action, [this.value = null]);
+ /** The mock object name, if any. */
+ final String mockName;
+
+ /** The method name. */
+ final String methodName;
+
+ /** The parameters. */
+ final List args;
+
+ /** The behavior that resulted. */
+ final _Action action;
+
+ /** The value that was returned (if no throw). */
+ final value;
+
+ LogEntry(this.mockName, this.methodName,
+ this.args, this.action, [this.value]) {
+ when = new Date.now();
+ }
+
+ String _pad2(int val) => (val >= 10 ? '$val' : '0$val');
+
+ String toString([Date baseTime]) {
+ Description d = new StringDescription();
+ if (baseTime == null) {
+ // Show absolute time.
+ d.add('${when.hour}:${_pad2(when.minute)}:'
+ '${_pad2(when.second)}.${when.millisecond}> ');
+ } else {
+ // Show relative time.
+ int delta = when.millisecondsSinceEpoch - baseTime.millisecondsSinceEpoch;
+ int secs = delta ~/ 1000;
+ int msecs = delta % 1000;
+ d.add('$secs.$msecs> ');
+ }
+ d.add('${_qualifiedName(mockName, methodName)}(');
+ for (var i = 0; i < args.length; i++) {
+ if (i != 0) d.add(', ');
+ d.addDescriptionOf(args[i]);
+ }
+ d.add(') ${action == _Action.THROW ? "threw" : "returned"} ');
+ d.addDescriptionOf(value);
+ return d.toString();
+ }
}
+/** Utility function for optionally qualified method names */
+String _qualifiedName(String owner, String method) {
+ if (owner == null) {
+ return method;
+ } else {
+ return '$owner.$method';
+ }
+}
+
/**
* We do verification on a list of [LogEntry]s. To allow chaining
* of calls to verify, we encapsulate such a list in the [LogEntryList]
@@ -250,26 +314,37 @@
*/
class LogEntryList {
final String filter;
- final List<LogEntry> logs;
- const LogEntryList(this.logs, [this.filter = null]);
+ List<LogEntry> logs;
+ LogEntryList([this.filter]) {
+ logs = new List<LogEntry>();
+ }
/** Add a [LogEntry] to the log. */
add(LogEntry entry) => logs.add(entry);
/**
* Create a new [LogEntryList] consisting of [LogEntry]s from
- * this list that match the specified [logfilter]. If [destructive]
+ * this list that match the specified [mockName] and [logFilter].
+ * If [mockName] is null, all entries will be checked. If [destructive]
* is true, the log entries are removed from the original list.
*/
- LogEntryList getMatches(CallMatcher logfilter, bool destructive) {
- LogEntryList rtn =
- new LogEntryList(new List<LogEntry>(), logfilter.toString());
+ LogEntryList getMatches(String mockName,
+ CallMatcher logFilter,
+ [Matcher actionMatcher,
+ bool destructive = false]) {
+ String filterName = _qualifiedName(mockName, logFilter.toString());
+ LogEntryList rtn = new LogEntryList(filterName);
for (var i = 0; i < logs.length; i++) {
LogEntry entry = logs[i];
- if (logfilter.matches(entry.name, entry.args)) {
- rtn.add(entry);
- if (destructive) {
- logs.removeRange(i--, 1);
+ if (mockName != null && mockName != entry.mockName) {
+ continue;
+ }
+ if (logFilter.matches(entry.methodName, entry.args)) {
+ if (actionMatcher == null || actionMatcher.matches(entry)) {
+ rtn.add(entry);
+ if (destructive) {
+ logs.removeRange(i--, 1);
+ }
}
}
}
@@ -285,6 +360,14 @@
expect(logs, matcher, filter, _mockFailureHandler);
return this;
}
+
+ String toString([Date baseTime]) {
+ String s = '';
+ for (var e in logs) {
+ s = '$s${e.toString(baseTime)}\n';
+ }
+ return s;
+ }
}
/**
@@ -316,79 +399,153 @@
mismatchDescription.add('was called ${log.length} times');
}
-/** [calledExactly] matches an exact number of calls. */
-Matcher calledExactly(count) {
+/** [happenedExactly] matches an exact number of calls. */
+Matcher happenedExactly(count) {
return new _TimesMatcher(count, count);
}
-/** [calledAtLeast] matches a minimum number of calls. */
-Matcher calledAtLeast(count) {
+/** [happenedAtLeast] matches a minimum number of calls. */
+Matcher happenedAtLeast(count) {
return new _TimesMatcher(count);
}
-/** [calledAtMost] matches a maximum number of calls. */
-Matcher calledAtMost(count) {
+/** [happenedAtMost] matches a maximum number of calls. */
+Matcher happenedAtMost(count) {
return new _TimesMatcher(0, count);
}
-/** [neverCalled] matches zero calls. */
-final Matcher neverCalled = const _TimesMatcher(0, 0);
+/** [neverHappened] matches zero calls. */
+final Matcher neverHappened = const _TimesMatcher(0, 0);
-/** [calledOnce] matches exactly one call. */
-final Matcher calledOnce = const _TimesMatcher(1, 1);
+/** [happenedOnce] matches exactly one call. */
+final Matcher happenedOnce = const _TimesMatcher(1, 1);
-/** [calledAtLeastOnce] matches one or more calls. */
-final Matcher calledAtLeastOnce = const _TimesMatcher(1);
+/** [happenedAtLeastOnce] matches one or more calls. */
+final Matcher happenedAtLeastOnce = const _TimesMatcher(1);
-/** [calledAtMostOnce] matches zero or one call. */
-final Matcher calledAtMostOnce = const _TimesMatcher(0, 1);
+/** [happenedAtMostOnce] matches zero or one call. */
+final Matcher happenedAtMostOnce = const _TimesMatcher(0, 1);
-/** Special values for use with [_ResultMatcher] [frequency]. */
-final int ALL = 0;
-final int SOME = 1;
-final int NONE = 2;
/**
* [_ResultMatcher]s are used to make assertions about the results
+ * of method calls. These can be used as optional parameters to [getLogs].
+ */
+class _ResultMatcher extends BaseMatcher {
+ final _Action action;
+ final Matcher value;
+
+ const _ResultMatcher(this.action, this.value);
+
+ bool matches(item) {
+ if (item is! LogEntry) {
+ return false;
+ }
+ // normalize the action; _PROXY is like _RETURN.
+ _Action eaction = item.action;
+ if (eaction == _Action.PROXY) {
+ eaction = _Action.RETURN;
+ }
+ return (eaction == action && value.matches(item.value));
+ }
+
+ Description describe(Description description) {
+ description.add(' to ');
+ if (action == _Action.RETURN || action == _Action.PROXY)
+ description.add('return ');
+ else
+ description.add('throw ');
+ return description.addDescriptionOf(value);
+ }
+
+ Description describeMismatch(item, Description mismatchDescription) {
+ if (item.action == _Action.RETURN || item.action == _Action.PROXY) {
+ mismatchDescription.add('returned ');
+ } else {
+ mismatchDescription.add('threw ');
+ }
+ mismatchDescription.add(item.value);
+ return mismatchDescription;
+ }
+}
+
+/**
+ *[returning] matches log entries where the call to a method returned
+ * a value that matched [value].
+ */
+Matcher returning(value) =>
+ new _ResultMatcher(_Action.RETURN, wrapMatcher(value));
+
+/**
+ *[throwing] matches log entrues where the call to a method threw
+ * a value that matched [value].
+ */
+Matcher throwing(value) =>
+ new _ResultMatcher(_Action.THROW, wrapMatcher(value));
+
+/** Special values for use with [_ResultSetMatcher] [frequency]. */
+class _Frequency {
+ /** Every call/throw must match */
+ static final ALL = const _Frequency._('ALL');
+
+ /** At least one call/throw must match. */
+ static final SOME = const _Frequency._('SOME');
+
+ /** No calls/throws should match. */
+ static final NONE = const _Frequency._('NONE');
+
+ const _Frequency._(this.name);
+
+ final String name;
+}
+
+/**
+ * [_ResultSetMatcher]s are used to make assertions about the results
* of method calls. When filtering an execution log by calling
- * [forThe], a [LogEntrySet] of matching call logs is returned;
- * [_ResultMatcher]s can then assert various things about this
+ * [getLogs], a [LogEntrySet] of matching call logs is returned;
+ * [_ResultSetMatcher]s can then assert various things about this
* (sub)set of logs.
+ *
+ * We could make this class use _ResultMatcher but it doesn't buy that
+ * match and adds some perf hit, so there is some duplication here.
*/
-class _ResultMatcher extends BaseMatcher {
- final int action;
- final value;
- final int frequency; // -1 for all, 0 for none, 1 for some.
+class _ResultSetMatcher extends BaseMatcher {
+ final _Action action;
+ final Matcher value;
+ final _Frequency frequency; // ALL, SOME, or NONE.
- const _ResultMatcher(this.action, this.value, this.frequency);
+ const _ResultSetMatcher(this.action, this.value, this.frequency);
bool matches(log) {
for (LogEntry entry in log) {
- // normalize the action; PROXY is like RETURN.
- int eaction = (entry.action == THROW) ? THROW : RETURN;
+ // normalize the action; _PROXY is like _RETURN.
+ _Action eaction = entry.action;
+ if (eaction == _Action.PROXY) {
+ eaction = _Action.RETURN;
+ }
if (eaction == action && value.matches(entry.value)) {
- if (frequency == NONE) {
+ if (frequency == _Frequency.NONE) {
return false;
- } else if (frequency == SOME) {
+ } else if (frequency == _Frequency.SOME) {
return true;
}
} else {
// Mismatch.
- if (frequency == ALL) { // We need just one mismatch to fail.
+ if (frequency == _Frequency.ALL) { // We need just one mismatch to fail.
return false;
}
}
}
- // If we get here, then if count is ALL we got all matches and
+ // If we get here, then if count is _ALL we got all matches and
// this is success; otherwise we got all mismatched which is
- // success for count == NONE and failure for count == SOME.
- return (frequency != SOME);
+ // success for count == _NONE and failure for count == _SOME.
+ return (frequency != _Frequency.SOME);
}
Description describe(Description description) {
description.add(' to ');
- description.add(frequency == ALL ? 'alway ' :
- (frequency == NONE ? 'never ' : 'sometimes '));
- if (action == RETURN || action == PROXY)
+ description.add(frequency == _Frequency.ALL ? 'alway ' :
+ (frequency == _Frequency.NONE ? 'never ' : 'sometimes '));
+ if (action == _Action.RETURN || action == __Action.PROXY)
description.add('return ');
else
description.add('throw ');
@@ -396,10 +553,10 @@
}
Description describeMismatch(log, Description mismatchDescription) {
- if (frequency != SOME) {
+ if (frequency != _Frequency.SOME) {
for (LogEntry entry in log) {
if (entry.action != action || !value.matches(entry.value)) {
- if (entry.action == RETURN || entry.action == PROXY)
+ if (entry.action == _Action.RETURN || entry.action == _Action.PROXY)
mismatchDescription.add('returned ');
else
mismatchDescription.add('threw ');
@@ -420,43 +577,46 @@
* a value that matched [value].
*/
Matcher alwaysReturned(value) =>
- new _ResultMatcher(RETURN, wrapMatcher(value), ALL);
+ new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.ALL);
/**
*[sometimeReturned] asserts that at least one matching call to a method
* returned a value that matched [value].
*/
Matcher sometimeReturned(value) =>
- new _ResultMatcher(RETURN, wrapMatcher(value), SOME);
+ new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.SOME);
/**
*[neverReturned] asserts that no matching calls to a method returned
* a value that matched [value].
*/
Matcher neverReturned(value) =>
- new _ResultMatcher(RETURN, wrapMatcher(value), NONE);
+ new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.NONE);
/**
*[alwaysThrew] asserts that all matching calls to a method threw
* a value that matched [value].
*/
Matcher alwaysThrew(value) =>
- new _ResultMatcher(THROW, wrapMatcher(value), ALL);
+ new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.ALL);
/**
*[sometimeThrew] asserts that at least one matching call to a method threw
* a value that matched [value].
*/
Matcher sometimeThrew(value) =>
- new _ResultMatcher(THROW, wrapMatcher(value), SOME);
+ new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.SOME);
/**
*[neverThrew] asserts that no matching call to a method threw
* a value that matched [value].
*/
Matcher neverThrew(value) =>
- new _ResultMatcher(THROW, wrapMatcher(value), NONE);
+ new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.NONE);
+/** The shared log used for named mocks. */
+LogEntryList sharedLog = null;
+
/**
* [Mock] is the base class for all mocked objects, with
* support for basic mocking.
@@ -479,9 +639,14 @@
* to some other implementation. This provides a way to implement 'spies'.
*
* You can then use the mock object. Once you are done, to verify the
- * behavior, use [forThe] to extract a relevant subset of method call
+ * behavior, use [getLogs] to extract a relevant subset of method call
* logs and apply [Matchers] to these through calling [verify].
*
+ * A Mock can be given a name when constructed. In this case instead of
+ * keeping its own log, it uses a shared log. This can be useful to get an
+ * audit trail of interleaved behavior. It is the responsibility of the user
+ * to ensure that mock names, if used, are unique.
+ *
* Limitations:
* - only positional parameters are supported (up to 10);
* - to mock getters you will need to include parentheses in the call
@@ -497,9 +662,9 @@
* m.add('foo');
* m.add('bar');
*
- * getLogs(m, callsTo('add', anything)).verify(calledExactly(2));
- * getLogs(m, callsTo('add', 'foo')).verify(calledOnce);
- * getLogs(m, callsTo('add', 'isNull)).verify(neverCalled);
+ * getLogs(m, callsTo('add', anything)).verify(happenedExactly(2));
+ * getLogs(m, callsTo('add', 'foo')).verify(happenedOnce);
+ * getLogs(m, callsTo('add', 'isNull)).verify(neverHappened);
*
* Note that we don't need to provide argument matchers for all arguments,
* but we do need to provide arguments for all matchers. So this is allowed:
@@ -530,15 +695,43 @@
*
*/
class Mock {
- Map<String,Behavior> behaviors; /** The set of [behavior]s supported. */
- LogEntryList log; /** The [log] of calls made. */
+ /** The mock name. Needed if the log is shared; optional otherwise. */
+ final String name;
- Mock() {
+ /** The set of [behavior]s supported. */
+ Map<String,Behavior> behaviors;
+
+ /** The [log] of calls made. Only used if [name] is null. */
+ LogEntryList log;
+
+ /** How to handle unknown method calls - swallow or throw. */
+ final bool throwIfNoBehavior = false;
+
+ /*
+ * Default constructor. Unknown method calls are allowed and logged,
+ * the mock has no name, and has its own log.
+ */
+ Mock() : throwIfNoBehavior = false, name = null {
+ log = new LogEntryList();
behaviors = new Map<String,Behavior>();
- log = new LogEntryList(new List<LogEntry>());
}
/**
+ * This constructor makes a mock that has a [name] and possibly uses
+ * a shared [log]. If [throwIfNoBehavior] is true, any calls to methods
+ * that have no defined behaviors will throw an exception; otherwise they
+ * will be allowed and logged (but will not do anything).
+ */
+ Mock.custom([this.name,
Siggi Cherem (dart-lang) 2012/07/09 22:11:05 please add tests for allocating Mock with a name a
gram 2012/07/09 22:35:29 Done.
+ this.log,
+ this.throwIfNoBehavior = false]) {
+ if (log == null) {
+ log = new LogEntryList();
+ }
+ behaviors = new Map<String,Behavior>();
+ }
+
+ /**
* [when] is used to create a new or extend an existing [Behavior].
* A [CallMatcher] [filter] must be supplied, and the [Behavior]s for
* that signature are returned (being created first if needed).
@@ -565,10 +758,17 @@
* return value. If we find no [Behavior] to apply an exception is
* thrown.
*/
- noSuchMethod(String name, List args) {
+ noSuchMethod(String method, List args) {
+ if (method.startsWith('get:')) {
+ method = 'get ${method.substring(4)}';
+ }
+ bool matchedMethodName = false;
for (String k in behaviors.getKeys()) {
Behavior b = behaviors[k];
- if (b.matches(name, args)) {
+ if (b.matcher.name == method) {
+ matchedMethodName = true;
+ }
+ if (b.matches(method, args)) {
List actions = b.actions;
if (actions == null || actions.length == 0) {
continue; // No return values left in this Behavior.
@@ -581,97 +781,98 @@
// (negation of) the number of times we returned the value.
if (--response.count == 0) {
actions.removeRange(0, 1);
- if (actions.length == 0) {
- // Remove the behavior. Note that in the future there
- // may be some value in preserving the behaviors for
- // auditing purposes (e.g. how many times was this behavior used?).
- // If we do decide to keep them and perf is an issue instead of
- // deleting we could move this to a separate list.
- behaviors.remove(k);
- }
}
// Do the response.
- var action = response.action;
+ _Action action = response.action;
var value = response.value;
- switch (action) {
- case RETURN:
- log.add(new LogEntry(name, args, action, value));
- return value;
- case THROW:
- log.add(new LogEntry(name, args, action, value));
- throw value;
- case PROXY:
- var rtn;
- switch (args.length) {
- case 0:
- rtn = value();
- break;
- case 1:
- rtn = value(args[0]);
- break;
- case 2:
- rtn = value(args[0], args[1]);
- break;
- case 3:
- rtn = value(args[0], args[1], args[2]);
- break;
- case 4:
- rtn = value(args[0], args[1], args[2], args[3]);
- break;
- case 5:
- rtn = value(args[0], args[1], args[2], args[3], args[4]);
- break;
- case 6:
- rtn = value(args[0], args[1], args[2], args[3],
- args[4], args[5]);
- break;
- case 7:
- rtn = value(args[0], args[1], args[2], args[3],
- args[4], args[5], args[6]);
- break;
- case 8:
- rtn = value(args[0], args[1], args[2], args[3],
- args[4], args[5], args[6], args[7]);
- break;
- case 9:
- rtn = value(args[0], args[1], args[2], args[3],
- args[4], args[5], args[6], args[7], args[8]);
- break;
- case 9:
- rtn = value(args[0], args[1], args[2], args[3],
- args[4], args[5], args[6], args[7], args[8], args[9]);
- break;
- default:
- throw new Exception(
- "Cannot proxy calls with more than 10 parameters");
- }
- log.add(new LogEntry(name, args, action, rtn));
- return rtn;
+ if (action == _Action.RETURN) {
+ log.add(new LogEntry(name, method, args, action, value));
+ return value;
+ } else if (action == _Action.THROW) {
+ log.add(new LogEntry(name, method, args, action, value));
+ throw value;
+ } else if (action == _Action.PROXY) {
+ var rtn;
+ switch (args.length) {
+ case 0:
+ rtn = value();
+ break;
+ case 1:
+ rtn = value(args[0]);
+ break;
+ case 2:
+ rtn = value(args[0], args[1]);
+ break;
+ case 3:
+ rtn = value(args[0], args[1], args[2]);
+ break;
+ case 4:
+ rtn = value(args[0], args[1], args[2], args[3]);
+ break;
+ case 5:
+ rtn = value(args[0], args[1], args[2], args[3], args[4]);
+ break;
+ case 6:
+ rtn = value(args[0], args[1], args[2], args[3],
+ args[4], args[5]);
+ break;
+ case 7:
+ rtn = value(args[0], args[1], args[2], args[3],
+ args[4], args[5], args[6]);
+ break;
+ case 8:
+ rtn = value(args[0], args[1], args[2], args[3],
+ args[4], args[5], args[6], args[7]);
+ break;
+ case 9:
+ rtn = value(args[0], args[1], args[2], args[3],
+ args[4], args[5], args[6], args[7], args[8]);
+ break;
+ case 9:
+ rtn = value(args[0], args[1], args[2], args[3],
+ args[4], args[5], args[6], args[7], args[8], args[9]);
+ break;
+ default:
+ throw new Exception(
+ "Cannot proxy calls with more than 10 parameters");
+ }
+ log.add(new LogEntry(name, method, args, action, rtn));
+ return rtn;
}
}
}
- throw new Exception('No behavior specified for method $name');
+ if (matchedMethodName) {
+ // User did specify behavior for this method, but all the
+ // actions are exhausted. This is considered an error.
+ throw new Exception('No more actions for method '
+ '${_qualifiedName(name, method)}');
+ } else if (throwIfNoBehavior) {
+ throw new Exception('No behavior specified for method '
+ '${_qualifiedName(name, method)}');
+ }
+ // User hasn't specified behavior for this method; we don't throw
+ // so we can underspecify.
+ log.add(new LogEntry(name, method, args, _Action.IGNORE));
}
/** [verifyZeroInteractions] returns true if no calls were made */
bool verifyZeroInteractions() => log.logs.length == 0;
-}
-/**
- * [getLogs] extracts all calls from the call log of [mock] that match the
- * [logFilter] [CallMatcher], and returns the matching list of
- * [LogEntry]s. If [destructive] is false (the default) the matching
- * calls are left in the mock object's log, else they are removed.
- * Removal allows us to verify a set of interactions and then verify
- * that there are no other interactions left.
- *
- * Typical usage:
- *
- * getLogs(mock, callsTo(...)).verify(...);
- */
-LogEntryList getLogs(Mock mock, CallMatcher logFilter,
- [bool destructive = false]) {
- return mock.log.getMatches(logFilter, destructive);
+ /**
+ * [getLogs] extracts all calls from the call log that match the
+ * [logFilter] [CallMatcher], and returns the matching list of
+ * [LogEntry]s. If [destructive] is false (the default) the matching
+ * calls are left in the log, else they are removed. Removal allows
+ * us to verify a set of interactions and then verify that there are
+ * no other interactions left. [actionMatcher] can be used to further
+ * restrict the returned logs based on the action the mock performed.
+ *
+ * Typical usage:
+ *
+ * getLogs(callsTo(...)).verify(...);
+ */
+ LogEntryList getLogs(CallMatcher logFilter, [Matcher actionMatcher,
+ bool destructive = false]) {
+ return log.getMatches(name, logFilter, actionMatcher, destructive);
+ }
}
-
-
« 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