| 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 |