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

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

Issue 16408019: Improved error messages from unittest. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 7 years, 6 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
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 * A simple mocking/spy library. 6 * A simple mocking/spy library.
7 * 7 *
8 * ## Installing ## 8 * ## Installing ##
9 * 9 *
10 * Use [pub][] to install this package. Add the following to your `pubspec.yaml` 10 * Use [pub][] to install this package. Add the following to your `pubspec.yaml`
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after
129 129
130 import 'matcher.dart'; 130 import 'matcher.dart';
131 131
132 /** 132 /**
133 * The error formatter for mocking is a bit different from the default one 133 * The error formatter for mocking is a bit different from the default one
134 * for unit testing; instead of the third argument being a 'reason' 134 * for unit testing; instead of the third argument being a 'reason'
135 * it is instead a [signature] describing the method signature filter 135 * it is instead a [signature] describing the method signature filter
136 * that was used to select the logs that were verified. 136 * that was used to select the logs that were verified.
137 */ 137 */
138 String _mockingErrorFormatter(actual, Matcher matcher, String signature, 138 String _mockingErrorFormatter(actual, Matcher matcher, String signature,
139 MatchState matchState, bool verbose) { 139 Map matchState, bool verbose) {
140 var description = new StringDescription(); 140 var description = new StringDescription();
141 description.add('Expected ${signature} ').addDescriptionOf(matcher). 141 description.add('Expected ${signature} ').addDescriptionOf(matcher).
142 add('\n but: '); 142 add('\n but: ');
143 matcher.describeMismatch(actual, description, matchState, verbose).add('.'); 143 matcher.describeMismatch(actual, description, matchState, verbose).add('.');
144 return description.toString(); 144 return description.toString();
145 } 145 }
146 146
147 /** 147 /**
148 * The failure handler for the [expect()] calls that occur in [verify()] 148 * The failure handler for the [expect()] calls that occur in [verify()]
149 * methods in the mock objects. This calls the real failure handler used 149 * methods in the mock objects. This calls the real failure handler used
150 * by the unit test library after formatting the error message with 150 * by the unit test library after formatting the error message with
151 * the custom formatter. 151 * the custom formatter.
152 */ 152 */
153 class _MockFailureHandler implements FailureHandler { 153 class _MockFailureHandler implements FailureHandler {
154 FailureHandler proxy; 154 FailureHandler proxy;
155 _MockFailureHandler(this.proxy); 155 _MockFailureHandler(this.proxy);
156 void fail(String reason) { 156 void fail(String reason) {
157 proxy.fail(reason); 157 proxy.fail(reason);
158 } 158 }
159 void failMatch(actual, Matcher matcher, String reason, 159 void failMatch(actual, Matcher matcher, String reason,
160 MatchState matchState, bool verbose) { 160 Map matchState, bool verbose) {
161 proxy.fail(_mockingErrorFormatter(actual, matcher, reason, 161 proxy.fail(_mockingErrorFormatter(actual, matcher, reason,
162 matchState, verbose)); 162 matchState, verbose));
163 } 163 }
164 } 164 }
165 165
166 _MockFailureHandler _mockFailureHandler = null; 166 _MockFailureHandler _mockFailureHandler = null;
167 167
168 /** Sentinel value for representing no argument. */ 168 /** Sentinel value for representing no argument. */
169 class _Sentinel { 169 class _Sentinel {
170 const _Sentinel(); 170 const _Sentinel();
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
281 } 281 }
282 d.add(')'); 282 d.add(')');
283 return d.toString(); 283 return d.toString();
284 } 284 }
285 285
286 /** 286 /**
287 * Given a [method] name and list of [arguments], return true 287 * Given a [method] name and list of [arguments], return true
288 * if it matches this [CallMatcher. 288 * if it matches this [CallMatcher.
289 */ 289 */
290 bool matches(String method, List arguments) { 290 bool matches(String method, List arguments) {
291 var matchState = new MatchState(); 291 var matchState = {};
292 if (!nameFilter.matches(method, matchState)) { 292 if (!nameFilter.matches(method, matchState)) {
293 return false; 293 return false;
294 } 294 }
295 var numArgs = (arguments == null) ? 0 : arguments.length; 295 var numArgs = (arguments == null) ? 0 : arguments.length;
296 if (numArgs < argMatchers.length) { 296 if (numArgs < argMatchers.length) {
297 throw new Exception("Less arguments than matchers for $method."); 297 throw new Exception("Less arguments than matchers for $method.");
298 } 298 }
299 for (var i = 0; i < argMatchers.length; i++) { 299 for (var i = 0; i < argMatchers.length; i++) {
300 if (!argMatchers[i].matches(arguments[i], matchState)) { 300 if (!argMatchers[i].matches(arguments[i], matchState)) {
301 return false; 301 return false;
(...skipping 232 matching lines...) Expand 10 before | Expand all | Expand 10 after
534 Matcher actionMatcher, 534 Matcher actionMatcher,
535 bool destructive = false]) { 535 bool destructive = false]) {
536 if (mockNameFilter == null) { 536 if (mockNameFilter == null) {
537 mockNameFilter = anything; 537 mockNameFilter = anything;
538 } else { 538 } else {
539 mockNameFilter = wrapMatcher(mockNameFilter); 539 mockNameFilter = wrapMatcher(mockNameFilter);
540 } 540 }
541 Function entryFilter = _makePredicate(logFilter); 541 Function entryFilter = _makePredicate(logFilter);
542 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); 542 String filterName = _qualifiedName(mockNameFilter, logFilter.toString());
543 LogEntryList rtn = new LogEntryList(filterName); 543 LogEntryList rtn = new LogEntryList(filterName);
544 MatchState matchState = new MatchState(); 544 var matchState = {};
545 for (var i = 0; i < logs.length; i++) { 545 for (var i = 0; i < logs.length; i++) {
546 LogEntry entry = logs[i]; 546 LogEntry entry = logs[i];
547 if (mockNameFilter.matches(entry.mockName, matchState) && 547 if (mockNameFilter.matches(entry.mockName, matchState) &&
548 entryFilter(entry)) { 548 entryFilter(entry)) {
549 if (actionMatcher == null || 549 if (actionMatcher == null ||
550 actionMatcher.matches(entry, matchState)) { 550 actionMatcher.matches(entry, matchState)) {
551 rtn.add(entry); 551 rtn.add(entry);
552 if (destructive) { 552 if (destructive) {
553 int startIndex = i--; 553 int startIndex = i--;
554 logs.removeRange(startIndex, startIndex + 1); 554 logs.removeRange(startIndex, startIndex + 1);
(...skipping 333 matching lines...) Expand 10 before | Expand all | Expand 10 after
888 List scratch = null; 888 List scratch = null;
889 int remainingCount = 0; 889 int remainingCount = 0;
890 if (isPreceding) { 890 if (isPreceding) {
891 scratch = new List(); 891 scratch = new List();
892 remainingCount = logs.length; 892 remainingCount = logs.length;
893 } 893 }
894 894
895 var keyIterator = keys.logs.iterator; 895 var keyIterator = keys.logs.iterator;
896 keyIterator.moveNext(); 896 keyIterator.moveNext();
897 LogEntry keyEntry = keyIterator.current; 897 LogEntry keyEntry = keyIterator.current;
898 MatchState matchState = new MatchState(); 898 Map matchState = {};
899 899
900 for (LogEntry logEntry in logs) { 900 for (LogEntry logEntry in logs) {
901 // If we have a log entry match, copy the saved matches from the 901 // If we have a log entry match, copy the saved matches from the
902 // scratch buffer into the return list, as well as the matching entry, 902 // scratch buffer into the return list, as well as the matching entry,
903 // if appropriate, and reset the scratch buffer. Continue processing 903 // if appropriate, and reset the scratch buffer. Continue processing
904 // from the next key entry. 904 // from the next key entry.
905 if (keyEntry == logEntry) { 905 if (keyEntry == logEntry) {
906 if (scratch != null) { 906 if (scratch != null) {
907 int numToCopy = scratch.length; 907 int numToCopy = scratch.length;
908 if (distance > 0 && distance < numToCopy) { 908 if (distance > 0 && distance < numToCopy) {
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
993 993
994 /** 994 /**
995 * [_TimesMatcher]s are used to make assertions about the number of 995 * [_TimesMatcher]s are used to make assertions about the number of
996 * times a method was called. 996 * times a method was called.
997 */ 997 */
998 class _TimesMatcher extends BaseMatcher { 998 class _TimesMatcher extends BaseMatcher {
999 final int min, max; 999 final int min, max;
1000 1000
1001 const _TimesMatcher(this.min, [this.max = -1]); 1001 const _TimesMatcher(this.min, [this.max = -1]);
1002 1002
1003 bool matches(logList, MatchState matchState) => logList.length >= min && 1003 bool matches(logList, Map matchState) => logList.length >= min &&
1004 (max < 0 || logList.length <= max); 1004 (max < 0 || logList.length <= max);
1005 1005
1006 Description describe(Description description) { 1006 Description describe(Description description) {
1007 description.add('to be called '); 1007 description.add('to be called ');
1008 if (max < 0) { 1008 if (max < 0) {
1009 description.add('at least $min'); 1009 description.add('at least $min');
1010 } else if (max == min) { 1010 } else if (max == min) {
1011 description.add('$max'); 1011 description.add('$max');
1012 } else if (min == 0) { 1012 } else if (min == 0) {
1013 description.add('at most $max'); 1013 description.add('at most $max');
1014 } else { 1014 } else {
1015 description.add('between $min and $max'); 1015 description.add('between $min and $max');
1016 } 1016 }
1017 return description.add(' times'); 1017 return description.add(' times');
1018 } 1018 }
1019 1019
1020 Description describeMismatch(logList, Description mismatchDescription, 1020 Description describeMismatch(logList, Description mismatchDescription,
1021 MatchState matchState, bool verbose) => 1021 Map matchState, bool verbose) =>
1022 mismatchDescription.add('was called ${logList.length} times'); 1022 mismatchDescription.add('was called ${logList.length} times');
1023 } 1023 }
1024 1024
1025 /** [happenedExactly] matches an exact number of calls. */ 1025 /** [happenedExactly] matches an exact number of calls. */
1026 Matcher happenedExactly(count) { 1026 Matcher happenedExactly(count) {
1027 return new _TimesMatcher(count, count); 1027 return new _TimesMatcher(count, count);
1028 } 1028 }
1029 1029
1030 /** [happenedAtLeast] matches a minimum number of calls. */ 1030 /** [happenedAtLeast] matches a minimum number of calls. */
1031 Matcher happenedAtLeast(count) { 1031 Matcher happenedAtLeast(count) {
(...skipping 20 matching lines...) Expand all
1052 /** 1052 /**
1053 * [_ResultMatcher]s are used to make assertions about the results 1053 * [_ResultMatcher]s are used to make assertions about the results
1054 * of method calls. These can be used as optional parameters to [getLogs]. 1054 * of method calls. These can be used as optional parameters to [getLogs].
1055 */ 1055 */
1056 class _ResultMatcher extends BaseMatcher { 1056 class _ResultMatcher extends BaseMatcher {
1057 final Action action; 1057 final Action action;
1058 final Matcher value; 1058 final Matcher value;
1059 1059
1060 const _ResultMatcher(this.action, this.value); 1060 const _ResultMatcher(this.action, this.value);
1061 1061
1062 bool matches(item, MatchState matchState) { 1062 bool matches(item, Map matchState) {
1063 if (item is! LogEntry) { 1063 if (item is! LogEntry) {
1064 return false; 1064 return false;
1065 } 1065 }
1066 // normalize the action; _PROXY is like _RETURN. 1066 // normalize the action; _PROXY is like _RETURN.
1067 Action eaction = item.action; 1067 Action eaction = item.action;
1068 if (eaction == Action.PROXY) { 1068 if (eaction == Action.PROXY) {
1069 eaction = Action.RETURN; 1069 eaction = Action.RETURN;
1070 } 1070 }
1071 return (eaction == action && value.matches(item.value, matchState)); 1071 return (eaction == action && value.matches(item.value, matchState));
1072 } 1072 }
1073 1073
1074 Description describe(Description description) { 1074 Description describe(Description description) {
1075 description.add(' to '); 1075 description.add(' to ');
1076 if (action == Action.RETURN || action == Action.PROXY) 1076 if (action == Action.RETURN || action == Action.PROXY)
1077 description.add('return '); 1077 description.add('return ');
1078 else 1078 else
1079 description.add('throw '); 1079 description.add('throw ');
1080 return description.addDescriptionOf(value); 1080 return description.addDescriptionOf(value);
1081 } 1081 }
1082 1082
1083 Description describeMismatch(item, Description mismatchDescription, 1083 Description describeMismatch(item, Description mismatchDescription,
1084 MatchState matchState, bool verbose) { 1084 Map matchState, bool verbose) {
1085 if (item.action == Action.RETURN || item.action == Action.PROXY) { 1085 if (item.action == Action.RETURN || item.action == Action.PROXY) {
1086 mismatchDescription.add('returned '); 1086 mismatchDescription.add('returned ');
1087 } else { 1087 } else {
1088 mismatchDescription.add('threw '); 1088 mismatchDescription.add('threw ');
1089 } 1089 }
1090 mismatchDescription.add(item.value); 1090 mismatchDescription.add(item.value);
1091 return mismatchDescription; 1091 return mismatchDescription;
1092 } 1092 }
1093 } 1093 }
1094 1094
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
1132 * We could make this class use _ResultMatcher but it doesn't buy that 1132 * We could make this class use _ResultMatcher but it doesn't buy that
1133 * match and adds some perf hit, so there is some duplication here. 1133 * match and adds some perf hit, so there is some duplication here.
1134 */ 1134 */
1135 class _ResultSetMatcher extends BaseMatcher { 1135 class _ResultSetMatcher extends BaseMatcher {
1136 final Action action; 1136 final Action action;
1137 final Matcher value; 1137 final Matcher value;
1138 final _Frequency frequency; // ALL, SOME, or NONE. 1138 final _Frequency frequency; // ALL, SOME, or NONE.
1139 1139
1140 const _ResultSetMatcher(this.action, this.value, this.frequency); 1140 const _ResultSetMatcher(this.action, this.value, this.frequency);
1141 1141
1142 bool matches(logList, MatchState matchState) { 1142 bool matches(logList, Map matchState) {
1143 for (LogEntry entry in logList) { 1143 for (LogEntry entry in logList) {
1144 // normalize the action; PROXY is like RETURN. 1144 // normalize the action; PROXY is like RETURN.
1145 Action eaction = entry.action; 1145 Action eaction = entry.action;
1146 if (eaction == Action.PROXY) { 1146 if (eaction == Action.PROXY) {
1147 eaction = Action.RETURN; 1147 eaction = Action.RETURN;
1148 } 1148 }
1149 if (eaction == action && value.matches(entry.value, matchState)) { 1149 if (eaction == action && value.matches(entry.value, matchState)) {
1150 if (frequency == _Frequency.NONE) { 1150 if (frequency == _Frequency.NONE) {
1151 matchState.state = { 1151 addStateInfo(matchState, {'entry': entry});
1152 'state' : matchState.state,
1153 'entry' : entry
1154 };
1155 return false; 1152 return false;
1156 } else if (frequency == _Frequency.SOME) { 1153 } else if (frequency == _Frequency.SOME) {
1157 return true; 1154 return true;
1158 } 1155 }
1159 } else { 1156 } else {
1160 // Mismatch. 1157 // Mismatch.
1161 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. 1158 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail.
1162 matchState.state = { 1159 addStateInfo(matchState, {'entry': entry});
1163 'state' : matchState.state,
1164 'entry' : entry
1165 };
1166 return false; 1160 return false;
1167 } 1161 }
1168 } 1162 }
1169 } 1163 }
1170 // If we get here, then if count is _ALL we got all matches and 1164 // If we get here, then if count is _ALL we got all matches and
1171 // this is success; otherwise we got all mismatched which is 1165 // this is success; otherwise we got all mismatched which is
1172 // success for count == _NONE and failure for count == _SOME. 1166 // success for count == _NONE and failure for count == _SOME.
1173 return (frequency != _Frequency.SOME); 1167 return (frequency != _Frequency.SOME);
1174 } 1168 }
1175 1169
1176 Description describe(Description description) { 1170 Description describe(Description description) {
1177 description.add(' to '); 1171 description.add(' to ');
1178 description.add(frequency == _Frequency.ALL ? 'alway ' : 1172 description.add(frequency == _Frequency.ALL ? 'alway ' :
1179 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); 1173 (frequency == _Frequency.NONE ? 'never ' : 'sometimes '));
1180 if (action == Action.RETURN || action == Action.PROXY) 1174 if (action == Action.RETURN || action == Action.PROXY)
1181 description.add('return '); 1175 description.add('return ');
1182 else 1176 else
1183 description.add('throw '); 1177 description.add('throw ');
1184 return description.addDescriptionOf(value); 1178 return description.addDescriptionOf(value);
1185 } 1179 }
1186 1180
1187 Description describeMismatch(logList, Description mismatchDescription, 1181 Description describeMismatch(logList, Description mismatchDescription,
1188 MatchState matchState, bool verbose) { 1182 Map matchState, bool verbose) {
1189 if (frequency != _Frequency.SOME) { 1183 if (frequency != _Frequency.SOME) {
1190 LogEntry entry = matchState.state['entry']; 1184 LogEntry entry = matchState['entry'];
1191 if (entry.action == Action.RETURN || entry.action == Action.PROXY) { 1185 if (entry.action == Action.RETURN || entry.action == Action.PROXY) {
1192 mismatchDescription.add('returned'); 1186 mismatchDescription.add('returned');
1193 } else { 1187 } else {
1194 mismatchDescription.add('threw'); 1188 mismatchDescription.add('threw');
1195 } 1189 }
1196 mismatchDescription.add(' value that '); 1190 mismatchDescription.add(' value that ');
1197 value.describeMismatch(entry.value, mismatchDescription, 1191 value.describeMismatch(entry.value, mismatchDescription,
1198 matchState.state['state'], verbose); 1192 matchState['state'], verbose);
1199 mismatchDescription.add(' at least once'); 1193 mismatchDescription.add(' at least once');
1200 } else { 1194 } else {
1201 mismatchDescription.add('never did'); 1195 mismatchDescription.add('never did');
1202 } 1196 }
1203 return mismatchDescription; 1197 return mismatchDescription;
1204 } 1198 }
1205 } 1199 }
1206 1200
1207 /** 1201 /**
1208 *[alwaysReturned] asserts that all matching calls to a method returned 1202 *[alwaysReturned] asserts that all matching calls to a method returned
(...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after
1362 try { 1356 try {
1363 var result = mirror.delegate(invocation); 1357 var result = mirror.delegate(invocation);
1364 log.add(new LogEntry(name, method, args, Action.PROXY, result)); 1358 log.add(new LogEntry(name, method, args, Action.PROXY, result));
1365 return result; 1359 return result;
1366 } catch (e) { 1360 } catch (e) {
1367 log.add(new LogEntry(name, method, args, Action.THROW, e)); 1361 log.add(new LogEntry(name, method, args, Action.THROW, e));
1368 throw e; 1362 throw e;
1369 } 1363 }
1370 } 1364 }
1371 bool matchedMethodName = false; 1365 bool matchedMethodName = false;
1372 MatchState matchState = new MatchState(); 1366 Map matchState = {};
1373 for (String k in _behaviors.keys) { 1367 for (String k in _behaviors.keys) {
1374 Behavior b = _behaviors[k]; 1368 Behavior b = _behaviors[k];
1375 if (b.matcher.nameFilter.matches(method, matchState)) { 1369 if (b.matcher.nameFilter.matches(method, matchState)) {
1376 matchedMethodName = true; 1370 matchedMethodName = true;
1377 } 1371 }
1378 if (b.matches(method, args)) { 1372 if (b.matches(method, args)) {
1379 List actions = b.actions; 1373 List actions = b.actions;
1380 if (actions == null || actions.length == 0) { 1374 if (actions == null || actions.length == 0) {
1381 continue; // No return values left in this Behavior. 1375 continue; // No return values left in this Behavior.
1382 } 1376 }
(...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after
1544 } 1538 }
1545 } 1539 }
1546 } 1540 }
1547 1541
1548 /** Clear both logs and behavior. */ 1542 /** Clear both logs and behavior. */
1549 void reset() { 1543 void reset() {
1550 resetBehavior(); 1544 resetBehavior();
1551 clearLogs(); 1545 clearLogs();
1552 } 1546 }
1553 } 1547 }
OLDNEW
« no previous file with comments | « pkg/scheduled_test/test/descriptor/file_test.dart ('k') | pkg/unittest/lib/src/basematcher.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698