Skip to content

Commit c9f36d5

Browse files
committed
✨ Added better error handling for to ensure NoSuchMethodErrors and TypeErrors not caught when from inside the test body
1 parent fbb323d commit c9f36d5

File tree

5 files changed

+205
-18
lines changed

5 files changed

+205
-18
lines changed

example/parameterized_test_example.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ void main() {
1313
2,
1414
3,
1515
],
16-
(int value) {
16+
(int value) {
1717
final result = value < 4;
1818
expect(result, true);
1919
},
@@ -41,7 +41,7 @@ void main() {
4141
parameterizedTest(
4242
'Example using enum as value',
4343
FruitEnum.values,
44-
(FruitEnum testEnum) {
44+
(FruitEnum testEnum) {
4545
expect(testEnum.name.length, testEnum.wordLength);
4646
},
4747
);
@@ -59,7 +59,7 @@ void main() {
5959
parameterizedTest(
6060
'Example of list of values from function',
6161
provideData(),
62-
(int value1, int value2, int sum) {
62+
(int value1, int value2, int sum) {
6363
expect(value1 + value2, sum);
6464
},
6565
);
@@ -72,7 +72,7 @@ void main() {
7272
['apple', 5],
7373
['banana', 6],
7474
],
75-
(String word, int length) {
75+
(String word, int length) {
7676
expect(word.length, length);
7777
},
7878
setUp: () {
@@ -95,7 +95,7 @@ void main() {
9595
200,
9696
300,
9797
],
98-
(int value) async {
98+
(int value) async {
9999
final millis = DateTime.now().millisecondsSinceEpoch;
100100
await Future<void>.delayed(Duration(milliseconds: value));
101101
final passed = DateTime.now().millisecondsSinceEpoch - millis;
@@ -107,9 +107,9 @@ void main() {
107107
// Test with CSV data
108108
parameterizedTest('Example of CSV data',
109109
const CsvToListConverter().convert('kiwi,4\r\napple,5\r\nbanana,6'),
110-
(String fruit, int length) {
111-
expect(fruit.length, length);
112-
});
110+
(String fruit, int length) {
111+
expect(fruit.length, length);
112+
});
113113
}
114114

115115
enum FruitEnum {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:stack_trace/stack_trace.dart';
2+
3+
/// Extension for [StackTrace] to determine if the stack trace is from within
4+
/// a test
5+
extension StackTraceExtension on StackTrace {
6+
/// Returns `true` if the stack trace is from within a test body
7+
bool get isInsideTestBody {
8+
final trace = Trace.from(this);
9+
10+
// We are looking frames from within side Function.apply
11+
final frames = trace.frames.take(
12+
trace.frames.indexWhere(
13+
(f) => f.member == 'Function._apply',
14+
),
15+
);
16+
17+
return !frames.every(
18+
(f) =>
19+
f.package == 'parameterized_test' ||
20+
f.package == 'test_api' ||
21+
f.isCore,
22+
);
23+
}
24+
}

lib/src/parameterized_test.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:meta/meta.dart';
22
import 'package:parameterized_test/src/errors/parameterized_error.dart';
3+
import 'package:parameterized_test/src/errors/stack_trace_extension.dart';
34
import 'package:parameterized_test/src/test_options/value_with_test_options.dart';
45
import 'package:test/test.dart';
56

@@ -164,11 +165,15 @@ class ParameterizedTestImpl implements ParameterizedTest {
164165
);
165166
}
166167
//ignore: avoid_catching_errors
167-
on NoSuchMethodError catch (e) {
168+
on NoSuchMethodError catch (e, s) {
169+
if (s.isInsideTestBody) rethrow;
170+
168171
throw ParameterizedError.fromNoSuchMethodError(e, value);
169172
}
170173
//ignore: avoid_catching_errors
171-
on TypeError {
174+
on TypeError catch (e, s) {
175+
if (s.isInsideTestBody) rethrow;
176+
172177
throw ParameterizedError.forTypeError(value, body);
173178
}
174179
},

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ environment:
88

99
dependencies:
1010
meta: ^1.10.1
11+
stack_trace: ^1.11.1
1112
test: ^1.24.6
1213

1314
dev_dependencies:

test/parameterized_test_test.dart

Lines changed: 165 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,24 @@ void main() {
9797

9898
test(
9999
'makeDescription return a String with '
100-
'formatted output by customDescriptionBuilder '
101-
'and complex object as group description', () {
100+
'formatted output by customDescriptionBuilder '
101+
'and complex object as group description', () {
102102
final values = [1, 2, 3];
103103

104104
Object? builder(
105-
Object? groupDescription,
106-
int index,
107-
List<dynamic> values,
108-
) {
105+
Object? groupDescription,
106+
int index,
107+
List<dynamic> values,
108+
) {
109109
return '[ $groupDescription | $index | ${values.length} ]';
110110
}
111111

112-
final result = pTest.makeDescription(DateTime(2024,04,17), 1, values, builder);
112+
final result = pTest.makeDescription(
113+
DateTime(2024, 04, 17),
114+
1,
115+
values,
116+
builder,
117+
);
113118

114119
expect(result, '[ 2024-04-17 00:00:00.000 | 1 | 3 ]');
115120
});
@@ -364,7 +369,9 @@ void main() {
364369
expect(testMock.testCaptures[1].description, 3);
365370
expect(testMock.testCaptures[2].description, 4);
366371
});
372+
});
367373

374+
group('parameterized test exception handling tests', () {
368375
test(
369376
'test throws ParameterizedError when length values and length '
370377
'function arguments dont match', () {
@@ -373,6 +380,7 @@ void main() {
373380
pTest('test', [1, 2, 3], (int value, int value2) => value + value2),
374381
throwsA(isA<ParameterizedError>()),
375382
);
383+
376384
expect(
377385
() => pTest(
378386
'test',
@@ -387,13 +395,48 @@ void main() {
387395
);
388396
});
389397

398+
test(
399+
'nested test throws ParameterizedError when length values and length '
400+
'function arguments dont match', () {
401+
expect(
402+
() => pTest(
403+
'test',
404+
[1, 2, 3],
405+
(int value) => pTest(
406+
'test',
407+
[1, 2, 3],
408+
(int value, int value2) => value + value2,
409+
),
410+
),
411+
throwsA(isA<ParameterizedError>()),
412+
);
413+
414+
expect(
415+
() => pTest(
416+
'test',
417+
[1],
418+
(int value) => pTest(
419+
'test',
420+
[
421+
[1, 2],
422+
[3, 4],
423+
[5, 6],
424+
],
425+
(int value) => value,
426+
),
427+
),
428+
throwsA(isA<ParameterizedError>()),
429+
);
430+
});
431+
390432
test(
391433
'test throws ParameterizedError when values types and '
392434
'function arguments types dont match', () {
393435
expect(
394436
() => pTest('test', [1, 2, 3], (String value) => value),
395437
throwsA(isA<ParameterizedError>()),
396438
);
439+
397440
expect(
398441
() => pTest(
399442
'test',
@@ -408,10 +451,124 @@ void main() {
408451
);
409452
});
410453

454+
test(
455+
'nested test throws ParameterizedError when values types and '
456+
'function arguments types dont match', () {
457+
expect(
458+
() => pTest(
459+
'test',
460+
[1, 2, 3],
461+
(int value) => pTest('test', [1, 2, 3], (String value) => value),
462+
),
463+
throwsA(isA<ParameterizedError>()),
464+
);
465+
466+
expect(
467+
() => pTest(
468+
'test',
469+
[1],
470+
(int value) => pTest(
471+
'test',
472+
[
473+
[1, 2],
474+
[3, 4],
475+
[5, 6],
476+
],
477+
(int value, String value2) => value + value2.length,
478+
),
479+
),
480+
throwsA(isA<ParameterizedError>()),
481+
);
482+
});
483+
484+
test('test doesnt catch TypeError when the test body throws the exception',
485+
() {
486+
expect(
487+
() => pTest('test', [1, 2, 3], (int value) {
488+
// Causes a TypeError for test
489+
// ignore: unused_local_variable
490+
final error = value as String;
491+
}),
492+
throwsA(isA<TypeError>()),
493+
);
494+
});
495+
496+
test(
497+
'nested test doesnt catch TypeError when the test body '
498+
'throws the exception', () {
499+
expect(
500+
() => pTest(
501+
'test',
502+
[1, 2, 3],
503+
(int value) => pTest('test', [1, 2, 3], (int value) {
504+
// Causes a TypeError for test
505+
// ignore: unused_local_variable
506+
final error = value as String;
507+
}),
508+
),
509+
throwsA(isA<TypeError>()),
510+
);
511+
});
512+
513+
test(
514+
'test doesnt catch NoSuchMethodError when the test body '
515+
'throws the exception', () {
516+
expect(
517+
() => pTest('test', [1, 2, 3], (int value) {
518+
const dynamic errorCauser = '';
519+
520+
// This is a dynamic call, so it will throw a NoSuchMethodError
521+
// ignore: avoid_dynamic_calls
522+
errorCauser.whoop(1, 2);
523+
}),
524+
throwsA(isA<NoSuchMethodError>()),
525+
);
526+
});
527+
528+
test(
529+
'nested test doesnt catch NoSuchMethodError when the test body '
530+
'throws the exception', () {
531+
expect(
532+
() => pTest(
533+
'test',
534+
[1, 2, 3],
535+
(int value) => pTest('test', [1, 2, 3], (int value) {
536+
const dynamic errorCauser = '';
537+
538+
// This is a dynamic call, so it will throw a NoSuchMethodError
539+
// ignore: avoid_dynamic_calls
540+
errorCauser.whoop(1, 2);
541+
}),
542+
),
543+
throwsA(isA<NoSuchMethodError>()),
544+
);
545+
});
546+
411547
test('test doesnt catch exceptions other than NoSuchMethodError, TypeError',
412548
() {
413549
expect(
414-
() => pTest('test', [1, 2, 3], (int value) => throw Exception('test')),
550+
() => pTest(
551+
'test',
552+
[1, 2, 3],
553+
(int value) => throw Exception('test'),
554+
),
555+
throwsA(isA<Exception>()),
556+
);
557+
});
558+
559+
test(
560+
'nested test doesnt catch exceptions other than '
561+
'NoSuchMethodError, TypeError', () {
562+
expect(
563+
() => pTest(
564+
'test',
565+
[1, 2, 3],
566+
(int value) => pTest(
567+
'test',
568+
[1, 2, 3],
569+
(int value) => throw Exception('test'),
570+
),
571+
),
415572
throwsA(isA<Exception>()),
416573
);
417574
});

0 commit comments

Comments
 (0)