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

Unified Diff: pkg/mock/lib/src/mock.dart

Issue 233243007: pkg/mock: restructure library layout (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 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 | « pkg/mock/lib/src/log_entry_list.dart ('k') | pkg/mock/lib/src/responder.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/mock/lib/src/mock.dart
diff --git a/pkg/mock/lib/src/mock.dart b/pkg/mock/lib/src/mock.dart
new file mode 100644
index 0000000000000000000000000000000000000000..29598b83915e1c752d24bcf4185551247ed75a50
--- /dev/null
+++ b/pkg/mock/lib/src/mock.dart
@@ -0,0 +1,325 @@
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library mock.mock;
+
+// TOOD(kevmoo): just use `Map`
+import 'dart:collection' show LinkedHashMap;
+import 'dart:mirrors';
+
+import 'package:matcher/matcher.dart';
+
+import 'action.dart';
+import 'behavior.dart';
+import 'call_matcher.dart';
+import 'log_entry.dart';
+import 'log_entry_list.dart';
+import 'responder.dart';
+import 'util.dart';
+
+/** The base class for all mocked objects. */
+@proxy
+class Mock {
+ /** The mock name. Needed if the log is shared; optional otherwise. */
+ final String name;
+
+ /** The set of [Behavior]s supported. */
+ final LinkedHashMap<String,Behavior> _behaviors;
+
+ /** How to handle unknown method calls - swallow or throw. */
+ final bool _throwIfNoBehavior;
+
+ /** For spys, the real object that we are spying on. */
+ final Object _realObject;
+
+ /** The [log] of calls made. Only used if [name] is null. */
+ LogEntryList log;
+
+ /** Whether to create an audit log or not. */
+ bool _logging;
+
+ bool get logging => _logging;
+ set logging(bool value) {
+ if (value && log == null) {
+ log = new LogEntryList();
+ }
+ _logging = value;
+ }
+
+ /**
+ * Default constructor. Unknown method calls are allowed and logged,
+ * the mock has no name, and has its own log.
+ */
+ Mock() :
+ _throwIfNoBehavior = false, log = null, name = null, _realObject = null,
+ _behaviors = new LinkedHashMap<String,Behavior>() {
+ logging = true;
+ }
+
+ /**
+ * 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).
+ * If [enableLogging] is false, no logging will be done initially (whether
+ * or not a [log] is supplied), but [logging] can be set to true later.
+ */
+ Mock.custom({this.name,
+ this.log,
+ throwIfNoBehavior: false,
+ enableLogging: true})
+ : _throwIfNoBehavior = throwIfNoBehavior, _realObject = null,
+ _behaviors = new LinkedHashMap<String,Behavior>() {
+ if (log != null && name == null) {
+ throw new Exception("Mocks with shared logs must have a name.");
+ }
+ logging = enableLogging;
+ }
+
+ /**
+ * This constructor creates a spy with no user-defined behavior.
+ * This is simply a proxy for a real object that passes calls
+ * through to that real object but captures an audit trail of
+ * calls made to the object that can be queried and validated
+ * later.
+ */
+ Mock.spy(this._realObject, {this.name, this.log})
+ : _behaviors = null,
+ _throwIfNoBehavior = true {
+ logging = true;
+ }
+
+ /**
+ * [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).
+ *
+ * Typical use case:
+ *
+ * mock.when(callsTo(...)).alwaysReturn(...);
+ */
+ Behavior when(CallMatcher logFilter) {
+ String key = logFilter.toString();
+ if (!_behaviors.containsKey(key)) {
+ Behavior b = new Behavior(logFilter);
+ _behaviors[key] = b;
+ return b;
+ } else {
+ return _behaviors[key];
+ }
+ }
+
+ /**
+ * This is the handler for method calls. We loop through the list
+ * of [Behavior]s, and find the first match that still has return
+ * values available, and then do the action specified by that
+ * return value. If we find no [Behavior] to apply an exception is
+ * thrown.
+ */
+ noSuchMethod(Invocation invocation) {
+ var method = MirrorSystem.getName(invocation.memberName);
+ var args = invocation.positionalArguments;
+ if (invocation.isGetter) {
+ method = 'get $method';
+ } else if (invocation.isSetter) {
+ method = 'set $method';
+ // Remove the trailing '='.
+ if (method[method.length-1] == '=') {
+ method = method.substring(0, method.length - 1);
+ }
+ }
+ if (_behaviors == null) { // Spy.
+ var mirror = reflect(_realObject);
+ try {
+ var result = mirror.delegate(invocation);
+ log.add(new LogEntry(name, method, args, Action.PROXY, result));
+ return result;
+ } catch (e) {
+ log.add(new LogEntry(name, method, args, Action.THROW, e));
+ throw e;
+ }
+ }
+ bool matchedMethodName = false;
+ Map matchState = {};
+ for (String k in _behaviors.keys) {
+ Behavior b = _behaviors[k];
+ if (b.matcher.nameFilter.matches(method, matchState)) {
+ 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.
+ }
+ // Get the first response.
+ Responder response = actions[0];
+ // If it is exhausted, remove it from the list.
+ // Note that for endlessly repeating values, we started the count at
+ // 0, so we get a potentially useful value here, which is the
+ // (negation of) the number of times we returned the value.
+ if (--response.count == 0) {
+ actions.removeRange(0, 1);
+ }
+ // Do the response.
+ Action action = response.action;
+ var value = response.value;
+ if (action == Action.RETURN) {
+ if (_logging && b.logging) {
+ log.add(new LogEntry(name, method, args, action, value));
+ }
+ return value;
+ } else if (action == Action.THROW) {
+ if (_logging && b.logging) {
+ log.add(new LogEntry(name, method, args, action, value));
+ }
+ throw value;
+ } else if (action == Action.PROXY) {
+ // TODO(gram): Replace all this with:
+ // var rtn = reflect(value).apply(invocation.positionalArguments,
+ // invocation.namedArguments);
+ // once that is supported.
+ 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.");
+ }
+ if (_logging && b.logging) {
+ log.add(new LogEntry(name, method, args, action, rtn));
+ }
+ return rtn;
+ }
+ }
+ }
+ 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)}.');
+ }
+ // Otherwise user hasn't specified behavior for this method; we don't throw
+ // so we can underspecify.
+ if (_logging) {
+ log.add(new LogEntry(name, method, args, Action.IGNORE));
+ }
+ }
+
+ /** [verifyZeroInteractions] returns true if no calls were made */
+ bool verifyZeroInteractions() {
+ if (log == null) {
+ // This means we created the mock with logging off and have never turned
+ // it on, so it doesn't make sense to verify behavior on such a mock.
+ throw new
+ Exception("Can't verify behavior when logging was never enabled.");
+ }
+ return log.logs.length == 0;
+ }
+
+ /**
+ * [getLogs] extracts all calls from the call log that match the
+ * [logFilter], 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.
+ * [logFilter] can be a [CallMatcher] or a predicate function that
+ * takes a [LogEntry] and returns a bool.
+ *
+ * Typical usage:
+ *
+ * getLogs(callsTo(...)).verify(...);
+ */
+ LogEntryList getLogs([CallMatcher logFilter,
+ Matcher actionMatcher,
+ bool destructive = false]) {
+ if (log == null) {
+ // This means we created the mock with logging off and have never turned
+ // it on, so it doesn't make sense to get logs from such a mock.
+ throw new
+ Exception("Can't retrieve logs when logging was never enabled.");
+ } else {
+ return log.getMatches(name, logFilter, actionMatcher, destructive);
+ }
+ }
+
+ /**
+ * Useful shorthand method that creates a [CallMatcher] from its arguments
+ * and then calls [getLogs].
+ */
+ LogEntryList calls(method,
+ [arg0 = NO_ARG,
+ arg1 = NO_ARG,
+ arg2 = NO_ARG,
+ arg3 = NO_ARG,
+ arg4 = NO_ARG,
+ arg5 = NO_ARG,
+ arg6 = NO_ARG,
+ arg7 = NO_ARG,
+ arg8 = NO_ARG,
+ arg9 = NO_ARG]) =>
+ getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4,
+ arg5, arg6, arg7, arg8, arg9));
+
+ /** Clear the behaviors for the Mock. */
+ void resetBehavior() => _behaviors.clear();
+
+ /** Clear the logs for the Mock. */
+ void clearLogs() {
+ if (log != null) {
+ if (name == null) { // This log is not shared.
+ log.logs.clear();
+ } else { // This log may be shared.
+ log.logs = log.logs.where((e) => e.mockName != name).toList();
+ }
+ }
+ }
+
+ /** Clear both logs and behavior. */
+ void reset() {
+ resetBehavior();
+ clearLogs();
+ }
+}
« no previous file with comments | « pkg/mock/lib/src/log_entry_list.dart ('k') | pkg/mock/lib/src/responder.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698