OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |