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

Side by Side Diff: pkg/mock/lib/src/mock.dart

Issue 1115483002: remove pkg/mock (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 5 years, 7 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 | « pkg/mock/lib/src/log_entry_list.dart ('k') | pkg/mock/lib/src/responder.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
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.
4
5 library mock.mock;
6
7 // TOOD(kevmoo): just use `Map`
8 import 'dart:collection' show LinkedHashMap;
9 import 'dart:mirrors';
10
11 import 'package:matcher/matcher.dart';
12
13 import 'action.dart';
14 import 'behavior.dart';
15 import 'call_matcher.dart';
16 import 'log_entry.dart';
17 import 'log_entry_list.dart';
18 import 'responder.dart';
19 import 'util.dart';
20
21 /** The base class for all mocked objects. */
22 @proxy
23 class Mock {
24 /** The mock name. Needed if the log is shared; optional otherwise. */
25 final String name;
26
27 /** The set of [Behavior]s supported. */
28 final LinkedHashMap<String, Behavior> _behaviors;
29
30 /** How to handle unknown method calls - swallow or throw. */
31 final bool _throwIfNoBehavior;
32
33 /** For spys, the real object that we are spying on. */
34 final Object _realObject;
35
36 /** The [log] of calls made. Only used if [name] is null. */
37 LogEntryList log;
38
39 /** Whether to create an audit log or not. */
40 bool _logging;
41
42 bool get logging => _logging;
43 set logging(bool value) {
44 if (value && log == null) {
45 log = new LogEntryList();
46 }
47 _logging = value;
48 }
49
50 /**
51 * Default constructor. Unknown method calls are allowed and logged,
52 * the mock has no name, and has its own log.
53 */
54 Mock() :
55 _throwIfNoBehavior = false, log = null, name = null, _realObject = null,
56 _behaviors = new LinkedHashMap<String,Behavior>() {
57 logging = true;
58 }
59
60 /**
61 * This constructor makes a mock that has a [name] and possibly uses
62 * a shared [log]. If [throwIfNoBehavior] is true, any calls to methods
63 * that have no defined behaviors will throw an exception; otherwise they
64 * will be allowed and logged (but will not do anything).
65 * If [enableLogging] is false, no logging will be done initially (whether
66 * or not a [log] is supplied), but [logging] can be set to true later.
67 */
68 Mock.custom({this.name,
69 this.log,
70 throwIfNoBehavior: false,
71 enableLogging: true})
72 : _throwIfNoBehavior = throwIfNoBehavior, _realObject = null,
73 _behaviors = new LinkedHashMap<String,Behavior>() {
74 if (log != null && name == null) {
75 throw new Exception("Mocks with shared logs must have a name.");
76 }
77 logging = enableLogging;
78 }
79
80 /**
81 * This constructor creates a spy with no user-defined behavior.
82 * This is simply a proxy for a real object that passes calls
83 * through to that real object but captures an audit trail of
84 * calls made to the object that can be queried and validated
85 * later.
86 */
87 Mock.spy(this._realObject, {this.name, this.log})
88 : _behaviors = null,
89 _throwIfNoBehavior = true {
90 logging = true;
91 }
92
93 /**
94 * [when] is used to create a new or extend an existing [Behavior].
95 * A [CallMatcher] [filter] must be supplied, and the [Behavior]s for
96 * that signature are returned (being created first if needed).
97 *
98 * Typical use case:
99 *
100 * mock.when(callsTo(...)).alwaysReturn(...);
101 */
102 Behavior when(CallMatcher logFilter) {
103 String key = logFilter.toString();
104 if (!_behaviors.containsKey(key)) {
105 Behavior b = new Behavior(logFilter);
106 _behaviors[key] = b;
107 return b;
108 } else {
109 return _behaviors[key];
110 }
111 }
112
113 /**
114 * This is the handler for method calls. We loop through the list
115 * of [Behavior]s, and find the first match that still has return
116 * values available, and then do the action specified by that
117 * return value. If we find no [Behavior] to apply an exception is
118 * thrown.
119 */
120 noSuchMethod(Invocation invocation) {
121 var method = MirrorSystem.getName(invocation.memberName);
122 var args = invocation.positionalArguments;
123 if (invocation.isGetter) {
124 method = 'get $method';
125 } else if (invocation.isSetter) {
126 method = 'set $method';
127 // Remove the trailing '='.
128 if (method[method.length - 1] == '=') {
129 method = method.substring(0, method.length - 1);
130 }
131 }
132 if (_behaviors == null) { // Spy.
133 var mirror = reflect(_realObject);
134 try {
135 var result = mirror.delegate(invocation);
136 log.add(new LogEntry(name, method, args, Action.PROXY, result));
137 return result;
138 } catch (e) {
139 log.add(new LogEntry(name, method, args, Action.THROW, e));
140 throw e;
141 }
142 }
143 bool matchedMethodName = false;
144 Map matchState = {};
145 for (String k in _behaviors.keys) {
146 Behavior b = _behaviors[k];
147 if (b.matcher.nameFilter.matches(method, matchState)) {
148 matchedMethodName = true;
149 }
150 if (b.matches(method, args)) {
151 List actions = b.actions;
152 if (actions == null || actions.length == 0) {
153 continue; // No return values left in this Behavior.
154 }
155 // Get the first response.
156 Responder response = actions[0];
157 // If it is exhausted, remove it from the list.
158 // Note that for endlessly repeating values, we started the count at
159 // 0, so we get a potentially useful value here, which is the
160 // (negation of) the number of times we returned the value.
161 if (--response.count == 0) {
162 actions.removeRange(0, 1);
163 }
164 // Do the response.
165 Action action = response.action;
166 var value = response.value;
167 if (action == Action.RETURN) {
168 if (_logging && b.logging) {
169 log.add(new LogEntry(name, method, args, action, value));
170 }
171 return value;
172 } else if (action == Action.THROW) {
173 if (_logging && b.logging) {
174 log.add(new LogEntry(name, method, args, action, value));
175 }
176 throw value;
177 } else if (action == Action.PROXY) {
178 var mir = reflect(value) as ClosureMirror;
179 var rtn = mir.invoke(#call, invocation.positionalArguments,
180 invocation.namedArguments).reflectee;
181 if (_logging && b.logging) {
182 log.add(new LogEntry(name, method, args, action, rtn));
183 }
184 return rtn;
185 }
186 }
187 }
188 if (matchedMethodName) {
189 // User did specify behavior for this method, but all the
190 // actions are exhausted. This is considered an error.
191 throw new Exception('No more actions for method '
192 '${qualifiedName(name, method)}.');
193 } else if (_throwIfNoBehavior) {
194 throw new Exception('No behavior specified for method '
195 '${qualifiedName(name, method)}.');
196 }
197 // Otherwise user hasn't specified behavior for this method; we don't throw
198 // so we can underspecify.
199 if (_logging) {
200 log.add(new LogEntry(name, method, args, Action.IGNORE));
201 }
202 }
203
204 /** [verifyZeroInteractions] returns true if no calls were made */
205 bool verifyZeroInteractions() {
206 if (log == null) {
207 // This means we created the mock with logging off and have never turned
208 // it on, so it doesn't make sense to verify behavior on such a mock.
209 throw new
210 Exception("Can't verify behavior when logging was never enabled.");
211 }
212 return log.logs.length == 0;
213 }
214
215 /**
216 * [getLogs] extracts all calls from the call log that match the
217 * [logFilter], and returns the matching list of [LogEntry]s. If
218 * [destructive] is false (the default) the matching calls are left
219 * in the log, else they are removed. Removal allows us to verify a
220 * set of interactions and then verify that there are no other
221 * interactions left. [actionMatcher] can be used to further
222 * restrict the returned logs based on the action the mock performed.
223 * [logFilter] can be a [CallMatcher] or a predicate function that
224 * takes a [LogEntry] and returns a bool.
225 *
226 * Typical usage:
227 *
228 * getLogs(callsTo(...)).verify(...);
229 */
230 LogEntryList getLogs([CallMatcher logFilter,
231 Matcher actionMatcher,
232 bool destructive = false]) {
233 if (log == null) {
234 // This means we created the mock with logging off and have never turned
235 // it on, so it doesn't make sense to get logs from such a mock.
236 throw new
237 Exception("Can't retrieve logs when logging was never enabled.");
238 } else {
239 return log.getMatches(name, logFilter, actionMatcher, destructive);
240 }
241 }
242
243 /**
244 * Useful shorthand method that creates a [CallMatcher] from its arguments
245 * and then calls [getLogs].
246 */
247 LogEntryList calls(method,
248 [arg0 = NO_ARG,
249 arg1 = NO_ARG,
250 arg2 = NO_ARG,
251 arg3 = NO_ARG,
252 arg4 = NO_ARG,
253 arg5 = NO_ARG,
254 arg6 = NO_ARG,
255 arg7 = NO_ARG,
256 arg8 = NO_ARG,
257 arg9 = NO_ARG]) =>
258 getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4,
259 arg5, arg6, arg7, arg8, arg9));
260
261 /** Clear the behaviors for the Mock. */
262 void resetBehavior() => _behaviors.clear();
263
264 /** Clear the logs for the Mock. */
265 void clearLogs() {
266 if (log != null) {
267 if (name == null) { // This log is not shared.
268 log.logs.clear();
269 } else { // This log may be shared.
270 log.logs = log.logs.where((e) => e.mockName != name).toList();
271 }
272 }
273 }
274
275 /** Clear both logs and behavior. */
276 void reset() {
277 resetBehavior();
278 clearLogs();
279 }
280 }
OLDNEW
« 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