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

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

Issue 10689148: CallMatchers can now be null, or take null for a method name. This might be (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 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
74 } 74 }
75 75
76 /** 76 /**
77 * A [CallMatcher] is a special matcher used to match method calls (i.e. 77 * A [CallMatcher] is a special matcher used to match method calls (i.e.
78 * a method name and set of arguments). It is not a [Matcher] like the 78 * a method name and set of arguments). It is not a [Matcher] like the
79 * unit test [Matcher], but instead represents a method name and a 79 * unit test [Matcher], but instead represents a method name and a
80 * collection of [Matcher]s, one per argument, that will be applied 80 * collection of [Matcher]s, one per argument, that will be applied
81 * to the parameters to decide if the method call is a match. 81 * to the parameters to decide if the method call is a match.
82 */ 82 */
83 class CallMatcher { 83 class CallMatcher {
84 String name; 84 Matcher nameFilter;
85 List<Matcher> argMatchers; 85 List<Matcher> argMatchers;
86 86
87 CallMatcher(this.name, [ 87 /**
88 * Constructor for [CallMatcher]. [name] can be null to
89 * match anything, or a literal [String], a predicate [Function],
90 * or a [Matcher]. The various arguments can be scalar values or
91 * [Matcher]s.
92 */
93 CallMatcher([name,
88 arg0 = _noArg, 94 arg0 = _noArg,
89 arg1 = _noArg, 95 arg1 = _noArg,
90 arg2 = _noArg, 96 arg2 = _noArg,
91 arg3 = _noArg, 97 arg3 = _noArg,
92 arg4 = _noArg, 98 arg4 = _noArg,
93 arg5 = _noArg, 99 arg5 = _noArg,
94 arg6 = _noArg, 100 arg6 = _noArg,
95 arg7 = _noArg, 101 arg7 = _noArg,
96 arg8 = _noArg, 102 arg8 = _noArg,
97 arg9 = _noArg]) { 103 arg9 = _noArg]) {
104 if (name == null) {
105 nameFilter = anything;
106 } else {
107 nameFilter = wrapMatcher(name);
108 }
98 argMatchers = new List<Matcher>(); 109 argMatchers = new List<Matcher>();
99 if (arg0 == _noArg) return; 110 if (arg0 == _noArg) return;
100 argMatchers.add(wrapMatcher(arg0)); 111 argMatchers.add(wrapMatcher(arg0));
101 if (arg1 == _noArg) return; 112 if (arg1 == _noArg) return;
102 argMatchers.add(wrapMatcher(arg1)); 113 argMatchers.add(wrapMatcher(arg1));
103 if (arg2 == _noArg) return; 114 if (arg2 == _noArg) return;
104 argMatchers.add(wrapMatcher(arg2)); 115 argMatchers.add(wrapMatcher(arg2));
105 if (arg3 == _noArg) return; 116 if (arg3 == _noArg) return;
106 argMatchers.add(wrapMatcher(arg3)); 117 argMatchers.add(wrapMatcher(arg3));
107 if (arg4 == _noArg) return; 118 if (arg4 == _noArg) return;
(...skipping 11 matching lines...) Expand all
119 } 130 }
120 131
121 /** 132 /**
122 * We keep our behavior specifications in a Map, which is keyed 133 * We keep our behavior specifications in a Map, which is keyed
123 * by the [CallMatcher]. To make the keys unique and to get a 134 * by the [CallMatcher]. To make the keys unique and to get a
124 * descriptive value for the [CallMatcher] we have this override 135 * descriptive value for the [CallMatcher] we have this override
125 * of [toString()]. 136 * of [toString()].
126 */ 137 */
127 String toString() { 138 String toString() {
128 Description d = new StringDescription(); 139 Description d = new StringDescription();
129 d.add(name).add('('); 140 d.addDescriptionOf(nameFilter);
141 d.add('(');
130 for (var i = 0; i < argMatchers.length; i++) { 142 for (var i = 0; i < argMatchers.length; i++) {
131 if (i > 0) d.add(', '); 143 if (i > 0) d.add(', ');
132 d.addDescriptionOf(argMatchers[i]); 144 d.addDescriptionOf(argMatchers[i]);
133 } 145 }
134 d.add(')'); 146 d.add(')');
135 return d.toString(); 147 return d.toString();
136 } 148 }
137 149
138 /** 150 /**
139 * Given a [method] name and list of [arguments], return true 151 * Given a [method] name and list of [arguments], return true
140 * if it matches this [CallMatcher. 152 * if it matches this [CallMatcher.
141 */ 153 */
142 bool matches(String method, List arguments) { 154 bool matches(String method, List arguments) {
143 if (method != this.name) { 155 if (!nameFilter.matches(method)) {
144 return false; 156 return false;
145 } 157 }
146 if (arguments.length < argMatchers.length) { 158 if (arguments.length < argMatchers.length) {
147 throw new Exception("Less arguments than matchers for $name"); 159 throw new Exception("Less arguments than matchers for $method");
148 } 160 }
149 for (var i = 0; i < argMatchers.length; i++) { 161 for (var i = 0; i < argMatchers.length; i++) {
150 if (!argMatchers[i].matches(arguments[i])) { 162 if (!argMatchers[i].matches(arguments[i])) {
151 return false; 163 return false;
152 } 164 }
153 } 165 }
154 return true; 166 return true;
155 } 167 }
156 } 168 }
157 169
158 /** [callsTo] returns a CallMatcher for the specified signature. */ 170 /**
159 CallMatcher callsTo(String method, [ 171 * Returns a [CallMatcher] for the specified signature. [method] can be
160 arg0 = _noArg, 172 * null to match anything, or a literal [String], a predicate [Function],
161 arg1 = _noArg, 173 * or a [Matcher]. The various arguments can be scalar values or [Matcher]s.
162 arg2 = _noArg, 174 */
163 arg3 = _noArg, 175 CallMatcher callsTo([method,
164 arg4 = _noArg, 176 arg0 = _noArg,
165 arg5 = _noArg, 177 arg1 = _noArg,
166 arg6 = _noArg, 178 arg2 = _noArg,
167 arg7 = _noArg, 179 arg3 = _noArg,
168 arg8 = _noArg, 180 arg4 = _noArg,
169 arg9 = _noArg]) { 181 arg5 = _noArg,
182 arg6 = _noArg,
183 arg7 = _noArg,
184 arg8 = _noArg,
185 arg9 = _noArg]) {
170 return new CallMatcher(method, arg0, arg1, arg2, arg3, arg4, 186 return new CallMatcher(method, arg0, arg1, arg2, arg3, arg4,
171 arg5, arg6, arg7, arg8, arg9); 187 arg5, arg6, arg7, arg8, arg9);
172 } 188 }
173 189
174 /** 190 /**
175 * A [Behavior] represents how a [Mock] will respond to one particular 191 * A [Behavior] represents how a [Mock] will respond to one particular
176 * type of method call. 192 * type of method call.
177 */ 193 */
178 class Behavior { 194 class Behavior {
179 CallMatcher matcher; // The method call matcher. 195 CallMatcher matcher; // The method call matcher.
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
292 if (i != 0) d.add(', '); 308 if (i != 0) d.add(', ');
293 d.addDescriptionOf(args[i]); 309 d.addDescriptionOf(args[i]);
294 } 310 }
295 d.add(') ${action == _Action.THROW ? "threw" : "returned"} '); 311 d.add(') ${action == _Action.THROW ? "threw" : "returned"} ');
296 d.addDescriptionOf(value); 312 d.addDescriptionOf(value);
297 return d.toString(); 313 return d.toString();
298 } 314 }
299 } 315 }
300 316
301 /** Utility function for optionally qualified method names */ 317 /** Utility function for optionally qualified method names */
302 String _qualifiedName(String owner, String method) { 318 String _qualifiedName(owner, String method) {
303 if (owner == null) { 319 if (owner == null) {
304 return method; 320 return method;
321 } else if (owner is Matcher) {
322 Description d = new StringDescription();
323 d.addDescriptionOf(owner);
324 d.add('.');
325 d.add(method);
326 return d.toString();
305 } else { 327 } else {
306 return '$owner.$method'; 328 return '$owner.$method';
307 } 329 }
308 } 330 }
309 331
310 /** 332 /**
311 * We do verification on a list of [LogEntry]s. To allow chaining 333 * We do verification on a list of [LogEntry]s. To allow chaining
312 * of calls to verify, we encapsulate such a list in the [LogEntryList] 334 * of calls to verify, we encapsulate such a list in the [LogEntryList]
313 * class. 335 * class.
314 */ 336 */
315 class LogEntryList { 337 class LogEntryList {
316 final String filter; 338 final String filter;
317 List<LogEntry> logs; 339 List<LogEntry> logs;
318 LogEntryList([this.filter]) { 340 LogEntryList([this.filter]) {
319 logs = new List<LogEntry>(); 341 logs = new List<LogEntry>();
320 } 342 }
321 343
322 /** Add a [LogEntry] to the log. */ 344 /** Add a [LogEntry] to the log. */
323 add(LogEntry entry) => logs.add(entry); 345 add(LogEntry entry) => logs.add(entry);
324 346
325 /** 347 /**
326 * Create a new [LogEntryList] consisting of [LogEntry]s from 348 * Create a new [LogEntryList] consisting of [LogEntry]s from
327 * this list that match the specified [mockName] and [logFilter]. 349 * this list that match the specified [mockNameFilter] and [logFilter].
328 * If [mockName] is null, all entries will be checked. If [destructive] 350 * [mockNameFilter] can be null, a [String], a predicate [Function],
329 * is true, the log entries are removed from the original list. 351 * or a [Matcher]. If [mockNameFilter] is null, only Mocks with no name
352 * will be checked.
353 * If [logFilter] is null, all entries in the log will be returned.
354 * If [destructive] is true, the log entries are removed from the
355 * original list.
330 */ 356 */
331 LogEntryList getMatches(String mockName, 357 LogEntryList getMatches([mockNameFilter,
332 CallMatcher logFilter, 358 CallMatcher logFilter,
333 [Matcher actionMatcher, 359 Matcher actionMatcher,
334 bool destructive = false]) { 360 bool destructive = false]) {
335 String filterName = _qualifiedName(mockName, logFilter.toString()); 361 mockNameFilter = wrapMatcher(mockNameFilter);
362 if (logFilter == null) {
363 logFilter = new CallMatcher();
364 }
365 String filterName = _qualifiedName(mockNameFilter, logFilter.toString());
336 LogEntryList rtn = new LogEntryList(filterName); 366 LogEntryList rtn = new LogEntryList(filterName);
337 for (var i = 0; i < logs.length; i++) { 367 for (var i = 0; i < logs.length; i++) {
338 LogEntry entry = logs[i]; 368 LogEntry entry = logs[i];
339 if (mockName != null && mockName != entry.mockName) { 369 if (!mockNameFilter.matches(entry.mockName)) {
340 continue; 370 continue;
341 } 371 }
342 if (logFilter.matches(entry.methodName, entry.args)) { 372 if (logFilter.matches(entry.methodName, entry.args)) {
343 if (actionMatcher == null || actionMatcher.matches(entry)) { 373 if (actionMatcher == null || actionMatcher.matches(entry)) {
344 rtn.add(entry); 374 rtn.add(entry);
345 if (destructive) { 375 if (destructive) {
346 logs.removeRange(i--, 1); 376 logs.removeRange(i--, 1);
347 } 377 }
348 } 378 }
349 } 379 }
(...skipping 350 matching lines...) Expand 10 before | Expand all | Expand 10 after
700 730
701 /** The set of [behavior]s supported. */ 731 /** The set of [behavior]s supported. */
702 Map<String,Behavior> behaviors; 732 Map<String,Behavior> behaviors;
703 733
704 /** The [log] of calls made. Only used if [name] is null. */ 734 /** The [log] of calls made. Only used if [name] is null. */
705 LogEntryList log; 735 LogEntryList log;
706 736
707 /** How to handle unknown method calls - swallow or throw. */ 737 /** How to handle unknown method calls - swallow or throw. */
708 final bool throwIfNoBehavior = false; 738 final bool throwIfNoBehavior = false;
709 739
710 /* 740 /**
711 * Default constructor. Unknown method calls are allowed and logged, 741 * Default constructor. Unknown method calls are allowed and logged,
712 * the mock has no name, and has its own log. 742 * the mock has no name, and has its own log.
713 */ 743 */
714 Mock() : throwIfNoBehavior = false, name = null { 744 Mock() : throwIfNoBehavior = false, name = null {
715 log = new LogEntryList(); 745 log = new LogEntryList();
716 behaviors = new Map<String,Behavior>(); 746 behaviors = new Map<String,Behavior>();
717 } 747 }
718 748
719 /** 749 /**
720 * This constructor makes a mock that has a [name] and possibly uses 750 * This constructor makes a mock that has a [name] and possibly uses
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
758 * return value. If we find no [Behavior] to apply an exception is 788 * return value. If we find no [Behavior] to apply an exception is
759 * thrown. 789 * thrown.
760 */ 790 */
761 noSuchMethod(String method, List args) { 791 noSuchMethod(String method, List args) {
762 if (method.startsWith('get:')) { 792 if (method.startsWith('get:')) {
763 method = 'get ${method.substring(4)}'; 793 method = 'get ${method.substring(4)}';
764 } 794 }
765 bool matchedMethodName = false; 795 bool matchedMethodName = false;
766 for (String k in behaviors.getKeys()) { 796 for (String k in behaviors.getKeys()) {
767 Behavior b = behaviors[k]; 797 Behavior b = behaviors[k];
768 if (b.matcher.name == method) { 798 if (b.matcher.nameFilter.matches(method)) {
769 matchedMethodName = true; 799 matchedMethodName = true;
770 } 800 }
771 if (b.matches(method, args)) { 801 if (b.matches(method, args)) {
772 List actions = b.actions; 802 List actions = b.actions;
773 if (actions == null || actions.length == 0) { 803 if (actions == null || actions.length == 0) {
774 continue; // No return values left in this Behavior. 804 continue; // No return values left in this Behavior.
775 } 805 }
776 // Get the first response. 806 // Get the first response.
777 Responder response = actions[0]; 807 Responder response = actions[0];
778 // If it is exhausted, remove it from the list. 808 // If it is exhausted, remove it from the list.
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
864 * [LogEntry]s. If [destructive] is false (the default) the matching 894 * [LogEntry]s. If [destructive] is false (the default) the matching
865 * calls are left in the log, else they are removed. Removal allows 895 * calls are left in the log, else they are removed. Removal allows
866 * us to verify a set of interactions and then verify that there are 896 * us to verify a set of interactions and then verify that there are
867 * no other interactions left. [actionMatcher] can be used to further 897 * no other interactions left. [actionMatcher] can be used to further
868 * restrict the returned logs based on the action the mock performed. 898 * restrict the returned logs based on the action the mock performed.
869 * 899 *
870 * Typical usage: 900 * Typical usage:
871 * 901 *
872 * getLogs(callsTo(...)).verify(...); 902 * getLogs(callsTo(...)).verify(...);
873 */ 903 */
874 LogEntryList getLogs(CallMatcher logFilter, [Matcher actionMatcher, 904 LogEntryList getLogs([CallMatcher logFilter,
875 bool destructive = false]) { 905 Matcher actionMatcher,
906 bool destructive = false]) {
876 return log.getMatches(name, logFilter, actionMatcher, destructive); 907 return log.getMatches(name, logFilter, actionMatcher, destructive);
877 } 908 }
878 } 909 }
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