Merge pull request #132 from srawlins/better-multiline
Better multiline call string representations
diff --git a/lib/src/mock.dart b/lib/src/mock.dart
index 7bb81a5..b8f6ab5 100644
--- a/lib/src/mock.dart
+++ b/lib/src/mock.dart
@@ -133,6 +133,18 @@
@override
String toString() => _givenName != null ? _givenName : runtimeType.toString();
+
+ String _realCallsToString() {
+ var stringRepresentations = _realCalls.map((call) => call.toString());
+ if (stringRepresentations.any((s) => s.contains('\n'))) {
+ // As each call contains newlines, put each on its own line, for better
+ // readability.
+ return stringRepresentations.join(',\n');
+ } else {
+ // A compact String should be perfect.
+ return stringRepresentations.join(', ');
+ }
+ }
}
typedef CallPair _ReturnsCannedResponse();
@@ -456,32 +468,50 @@
@override
String toString() {
+ var argString = '';
var args = invocation.positionalArguments
- .map((v) => v == null ? "null" : v.toString())
- .join(", ");
+ .map((v) => v == null ? "null" : v.toString());
+ if (args.any((arg) => arg.contains('\n'))) {
+ // As one or more arg contains newlines, put each on its own line, and
+ // indent each, for better readability.
+ argString += '\n' +
+ args
+ .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m'))
+ .join(',\n');
+ } else {
+ // A compact String should be perfect.
+ argString += args.join(', ');
+ }
if (invocation.namedArguments.isNotEmpty) {
- var namedArgs = invocation.namedArguments.keys
- .map((key) =>
- "${_symbolToString(key)}: ${invocation.namedArguments[key]}")
- .join(", ");
- if (args.isNotEmpty) args += ", ";
- args += "{$namedArgs}";
+ if (argString.isNotEmpty) argString += ', ';
+ var namedArgs = invocation.namedArguments.keys.map((key) =>
+ '${_symbolToString(key)}: ${invocation.namedArguments[key]}');
+ if (namedArgs.any((arg) => arg.contains('\n'))) {
+ // As one or more arg contains newlines, put each on its own line, and
+ // indent each, for better readability.
+ namedArgs = namedArgs
+ .map((arg) => arg.splitMapJoin('\n', onNonMatch: (m) => ' $m'));
+ argString += '{\n${namedArgs.join(',\n')}}';
+ } else {
+ // A compact String should be perfect.
+ argString += '{${namedArgs.join(', ')}}';
+ }
}
var method = _symbolToString(invocation.memberName);
if (invocation.isMethod) {
- method = "$method($args)";
+ method = '$method($argString)';
} else if (invocation.isGetter) {
- method = "$method";
+ method = '$method';
} else if (invocation.isSetter) {
- method = "$method=$args";
+ method = '$method=$argString';
} else {
throw new StateError(
'Invocation should be getter, setter or a method call.');
}
- var verifiedText = verified ? "[VERIFIED] " : "";
- return "$verifiedText$mock.$method";
+ var verifiedText = verified ? '[VERIFIED] ' : '';
+ return '$verifiedText$mock.$method';
}
// This used to use MirrorSystem, which cleans up the Symbol() wrapper.
@@ -551,7 +581,7 @@
if (mock._realCalls.isEmpty) {
message = "No matching calls (actually, no calls at all).";
} else {
- var otherCalls = mock._realCalls.join(", ");
+ var otherCalls = mock._realCallsToString();
message = "No matching calls. All calls: $otherCalls";
}
fail("$message\n"
@@ -559,7 +589,7 @@
"`verifyNever(...);`.)");
}
if (never && matchingInvocations.isNotEmpty) {
- var calls = mock._realCalls.join(", ");
+ var calls = mock._realCallsToString();
fail("Unexpected calls. All calls: $calls");
}
matchingInvocations.forEach((inv) {
diff --git a/test/verify_test.dart b/test/verify_test.dart
index eab950a..9c1bcf1 100644
--- a/test/verify_test.dart
+++ b/test/verify_test.dart
@@ -21,6 +21,7 @@
String methodWithoutArgs() => 'Real';
String methodWithNormalArgs(int x) => 'Real';
String methodWithListArgs(List<int> x) => 'Real';
+ String methodWithOptionalArg([int x]) => 'Real';
String methodWithPositionalArgs(int x, [int y]) => 'Real';
String methodWithNamedArgs(int x, {int y}) => 'Real';
String methodWithOnlyNamedArgs({int y, int z}) => 'Real';
@@ -29,6 +30,24 @@
set setter(String arg) {
throw new StateError('I must be mocked');
}
+
+ String methodWithLongArgs(LongToString a, LongToString b,
+ {LongToString c, LongToString d}) =>
+ 'Real';
+}
+
+class LongToString {
+ final List aList;
+ final Map aMap;
+ final String aString;
+
+ LongToString(this.aList, this.aMap, this.aString);
+
+ String toString() => 'LongToString<\n'
+ ' aList: $aList\n'
+ ' aMap: $aMap\n'
+ ' aString: $aString\n'
+ '>';
}
class MockedClass extends Mock implements RealClass {}
@@ -179,51 +198,6 @@
verify(mock.setter = 'A');
});
- test(
- 'should fail when no matching call is found, '
- 'and there are no unmatched calls', () {
- expectFail(
- 'No matching calls (actually, no calls at all).\n'
- '$noMatchingCallsFooter', () {
- verify(mock.methodWithNormalArgs(43));
- });
- });
-
- test(
- 'should fail when no matching call is found, '
- 'and there is one unmatched call', () {
- mock.methodWithNormalArgs(42);
- expectFail(
- 'No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n'
- '$noMatchingCallsFooter', () {
- verify(mock.methodWithNormalArgs(43));
- });
- });
-
- test(
- 'should fail when no matching call is found, '
- 'and there are multiple unmatched calls', () {
- mock.methodWithNormalArgs(41);
- mock.methodWithNormalArgs(42);
- expectFail(
- 'No matching calls. All calls: '
- 'MockedClass.methodWithNormalArgs(41), MockedClass.methodWithNormalArgs(42)\n'
- '$noMatchingCallsFooter', () {
- verify(mock.methodWithNormalArgs(43));
- });
- });
-
- test(
- 'should fail when no matching call is found, '
- 'and unmatched calls have only named args', () {
- mock.methodWithOnlyNamedArgs(y: 1);
- expectFail(
- 'No matching calls. All calls: MockedClass.methodWithOnlyNamedArgs({y: 1})\n'
- '$noMatchingCallsFooter', () {
- verify(mock.methodWithOnlyNamedArgs());
- });
- });
-
test('should throw meaningful errors when verification is interrupted', () {
var badHelper = () => throw 'boo';
try {
@@ -248,6 +222,56 @@
});
});
+ group('verify should fail when no matching call is found', () {
+ test('and there are no unmatched calls', () {
+ expectFail(
+ 'No matching calls (actually, no calls at all).\n'
+ '$noMatchingCallsFooter', () {
+ verify(mock.methodWithNormalArgs(43));
+ });
+ });
+
+ test('and there is one unmatched call', () {
+ mock.methodWithNormalArgs(42);
+ expectFail(
+ 'No matching calls. All calls: MockedClass.methodWithNormalArgs(42)\n'
+ '$noMatchingCallsFooter', () {
+ verify(mock.methodWithNormalArgs(43));
+ });
+ });
+
+ test('and there is one unmatched call without args', () {
+ mock.methodWithOptionalArg();
+ expectFail(
+ 'No matching calls. All calls: MockedClass.methodWithOptionalArg()\n'
+ '$noMatchingCallsFooter', () {
+ verify(mock.methodWithOptionalArg(43));
+ });
+ });
+
+ test('and there are multiple unmatched calls', () {
+ mock.methodWithNormalArgs(41);
+ mock.methodWithNormalArgs(42);
+ expectFail(
+ 'No matching calls. All calls: '
+ 'MockedClass.methodWithNormalArgs(41), '
+ 'MockedClass.methodWithNormalArgs(42)\n'
+ '$noMatchingCallsFooter', () {
+ verify(mock.methodWithNormalArgs(43));
+ });
+ });
+
+ test('and unmatched calls have only named args', () {
+ mock.methodWithOnlyNamedArgs(y: 1);
+ expectFail(
+ 'No matching calls. All calls: '
+ 'MockedClass.methodWithOnlyNamedArgs({y: 1})\n'
+ '$noMatchingCallsFooter', () {
+ verify(mock.methodWithOnlyNamedArgs());
+ });
+ });
+ });
+
group('verify qualifies', () {
group('unqualified as at least one', () {
test('zero fails', () {
@@ -475,4 +499,43 @@
});
});
});
+
+ group('multiline toStrings on objects', () {
+ test(
+ '"No matching calls" message visibly separates unmatched calls, '
+ 'if an arg\'s string representations is multiline', () {
+ mock.methodWithLongArgs(new LongToString([1, 2], {1: 'a', 2: 'b'}, 'c'),
+ new LongToString([4, 5], {3: 'd', 4: 'e'}, 'f'));
+ mock.methodWithLongArgs(null, null,
+ c: new LongToString([5, 6], {5: 'g', 6: 'h'}, 'i'),
+ d: new LongToString([7, 8], {7: 'j', 8: 'k'}, 'l'));
+ expectFail(
+ 'No matching calls. All calls: '
+ 'MockedClass.methodWithLongArgs(\n'
+ ' LongToString<\n'
+ ' aList: [1, 2]\n'
+ ' aMap: {1: a, 2: b}\n'
+ ' aString: c\n'
+ ' >,\n'
+ ' LongToString<\n'
+ ' aList: [4, 5]\n'
+ ' aMap: {3: d, 4: e}\n'
+ ' aString: f\n'
+ ' >),\n'
+ 'MockedClass.methodWithLongArgs(null, null, {\n'
+ ' c: LongToString<\n'
+ ' aList: [5, 6]\n'
+ ' aMap: {5: g, 6: h}\n'
+ ' aString: i\n'
+ ' >,\n'
+ ' d: LongToString<\n'
+ ' aList: [7, 8]\n'
+ ' aMap: {7: j, 8: k}\n'
+ ' aString: l\n'
+ ' >})\n'
+ '$noMatchingCallsFooter', () {
+ verify(mock.methodWithNormalArgs(43));
+ });
+ });
+ });
}