MockBuilder: Improve bad-path messaging and tests.
Add cases for attempted mocking of extensions, and bad GenerateMocks construction.
Break out some more tests into smaller test cases.
Rename a test helper to be private: _expectBuilderThrows; private helps to indicate if it becomes unused.
PiperOrigin-RevId: 315393913
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index e1fe972..96465ef 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -52,10 +52,12 @@
if (annotation == null) continue;
final generateMocksValue = annotation.computeConstantValue();
// TODO(srawlins): handle `generateMocksValue == null`?
+ // I am unable to think of a case which results in this situation.
final classesField = generateMocksValue.getField('classes');
if (classesField.isNull) {
throw InvalidMockitoAnnotationException(
- 'The "classes" argument has unknown types');
+ 'The GenerateMocks "classes" argument is missing, includes an '
+ 'unknown type, or includes an extension');
}
classesToMock.addAll(classesField.toListValue());
}
diff --git a/test/builder_test.dart b/test/builder_test.dart
index fafa3a1..3798bff 100644
--- a/test/builder_test.dart
+++ b/test/builder_test.dart
@@ -44,59 +44,51 @@
void main() {
test(
- 'generates mock for an imported class but does not override private '
- 'or static methods or methods w/ zero parameters', () async {
- await _testWithNonNullable(
- {
- ...annotationsAsset,
- ...simpleTestAsset,
- 'foo|lib/foo.dart': dedent(r'''
- class Foo {
- dynamic a() => 7;
- int _b(int x) => 8;
- static int c(int y) => 9;
- }
- '''),
- },
- outputs: {
- 'foo|test/foo_test.mocks.dart': dedent(r'''
- import 'package:mockito/mockito.dart' as _i1;
- import 'package:foo/foo.dart' as _i2;
-
- /// A class which mocks [Foo].
- ///
- /// See the documentation for Mockito's code generation for more information.
- class MockFoo extends _i1.Mock implements _i2.Foo {}
- '''),
- },
+ 'generates a mock class but does not override methods w/ zero parameters',
+ () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo {
+ dynamic a() => 7;
+ }
+ '''),
+ _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'),
);
});
- test(
- 'generates mock for an imported class but does not override any '
- 'extension methods', () async {
- await _testWithNonNullable(
- {
- ...annotationsAsset,
- ...simpleTestAsset,
- 'foo|lib/foo.dart': dedent(r'''
- extension X on Foo {
- dynamic x(int m, String n) => n + 1;
- }
- class Foo {}
- '''),
- },
- outputs: {
- 'foo|test/foo_test.mocks.dart': dedent(r'''
- import 'package:mockito/mockito.dart' as _i1;
- import 'package:foo/foo.dart' as _i2;
+ test('generates a mock class but does not override private methods',
+ () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo {
+ int _b(int x) => 8;
+ }
+ '''),
+ _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'),
+ );
+ });
- /// A class which mocks [Foo].
- ///
- /// See the documentation for Mockito's code generation for more information.
- class MockFoo extends _i1.Mock implements _i2.Foo {}
- '''),
- },
+ test('generates a mock class but does not override static methods', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo {
+ static int c(int y) => 9;
+ }
+ '''),
+ _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'),
+ );
+ });
+
+ test('generates a mock class but does not override any extension methods',
+ () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ extension X on Foo {
+ dynamic x(int m, String n) => n + 1;
+ }
+ class Foo {}
+ '''),
+ _containsAllOf('class MockFoo extends _i1.Mock implements _i2.Foo {}'),
);
});
@@ -185,23 +177,12 @@
});
test('generates generic mock classes', () async {
- await _testWithNonNullable(
- {
- ...annotationsAsset,
- 'foo|lib/foo.dart': dedent(r'''
- class Foo<T, U> {}
- '''),
- 'foo|test/foo_test.dart': '''
- import 'package:foo/foo.dart';
- import 'package:mockito/annotations.dart';
- @GenerateMocks([Foo])
- void main() {}
- '''
- },
- outputs: {
- 'foo|test/foo_test.mocks.dart': _containsAllOf(
- 'class MockFoo<T, U> extends _i1.Mock implements _i2.Foo<T, U> {}'),
- },
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo<T, U> {}
+ '''),
+ _containsAllOf(
+ 'class MockFoo<T, U> extends _i1.Mock implements _i2.Foo<T, U> {}'),
);
});
@@ -229,35 +210,30 @@
);
});
- test('writes non-interface types w/o imports', () async {
- await _testWithNonNullable(
- {
- ...annotationsAsset,
- ...simpleTestAsset,
- 'foo|lib/foo.dart': dedent(r'''
- class Foo<T> {
- void f(dynamic a, int b) {}
- void g(T c) {}
- void h<U>(U d) {}
- }
- '''),
- },
- outputs: {
- 'foo|test/foo_test.mocks.dart': dedent(r'''
- import 'package:mockito/mockito.dart' as _i1;
- import 'package:foo/foo.dart' as _i2;
+ test('writes dynamic, void w/o import prefix', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo {
+ void m(dynamic a, int b) {}
+ }
+ '''),
+ _containsAllOf(
+ 'void m(dynamic a, int? b) =>',
+ 'super.noSuchMethod(Invocation.method(#m, [a, b]));',
+ ),
+ );
+ });
- /// A class which mocks [Foo].
- ///
- /// See the documentation for Mockito's code generation for more information.
- class MockFoo<T> extends _i1.Mock implements _i2.Foo<T> {
- void f(dynamic a, int? b) =>
- super.noSuchMethod(Invocation.method(#f, [a, b]));
- void g(T? c) => super.noSuchMethod(Invocation.method(#g, [c]));
- void h<U>(U? d) => super.noSuchMethod(Invocation.method(#h, [d]));
+ test('writes type variables types w/o import prefixes', () async {
+ await _expectSingleNonNullableOutput(
+ dedent(r'''
+ class Foo {
+ void m<T>(T a) {}
}
'''),
- },
+ _containsAllOf(
+ 'void m<T>(T? a) => super.noSuchMethod(Invocation.method(#m, [a]));',
+ ),
);
});
@@ -1031,8 +1007,26 @@
);
});
+ test('throws when GenerateMocks is missing an argument', () async {
+ _expectBuilderThrows(
+ assets: {
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {}
+ '''),
+ 'foo|test/foo_test.dart': dedent('''
+ import 'package:mockito/annotations.dart';
+ // Missing required argument to GenerateMocks.
+ @GenerateMocks()
+ void main() {}
+ '''),
+ },
+ message: contains('The GenerateMocks "classes" argument is missing'),
+ );
+ });
+
test('throws when GenerateMocks references an unresolved type', () async {
- expectBuilderThrows(
+ _expectBuilderThrows(
assets: {
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
@@ -1045,19 +1039,18 @@
void main() {}
'''),
},
- message: 'The "classes" argument has unknown types',
+ message: contains('includes an unknown type'),
);
});
test('throws when GenerateMocks references a non-type', () async {
- expectBuilderThrows(
+ _expectBuilderThrows(
assets: {
...annotationsAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {}
'''),
'foo|test/foo_test.dart': dedent('''
- // missing foo.dart import.
import 'package:mockito/annotations.dart';
@GenerateMocks([7])
void main() {}
@@ -1068,7 +1061,7 @@
});
test('throws when GenerateMocks references a typedef', () async {
- expectBuilderThrows(
+ _expectBuilderThrows(
assets: {
...annotationsAsset,
...simpleTestAsset,
@@ -1081,7 +1074,7 @@
});
test('throws when GenerateMocks references an enum', () async {
- expectBuilderThrows(
+ _expectBuilderThrows(
assets: {
...annotationsAsset,
...simpleTestAsset,
@@ -1093,6 +1086,19 @@
);
});
+ test('throws when GenerateMocks references an extension', () async {
+ _expectBuilderThrows(
+ assets: {
+ ...annotationsAsset,
+ ...simpleTestAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ extension Foo on String {}
+ '''),
+ },
+ message: contains('includes an extension'),
+ );
+ });
+
test('given a pre-non-nullable library, does not override any members',
() async {
await _testPreNonNullable(
@@ -1133,7 +1139,7 @@
/// Test [MockBuilder] in a package which has opted into the non-nullable type
/// system, and with the non-nullable experiment enabled.
Future<void> _testWithNonNullable(Map<String, String> sourceAssets,
- {Map<String, /*String|Matcher<String>*/ dynamic> outputs}) async {
+ {Map<String, /*String|Matcher<List<int>>*/ dynamic> outputs}) async {
var packageConfig = PackageConfig([
Package('foo', Uri.file('/foo/'),
packageUriRoot: Uri.file('/foo/lib/'),
@@ -1151,7 +1157,7 @@
/// enabled.
Future<void> _expectSingleNonNullableOutput(
String sourceAssetText,
- /*String|Matcher<String>*/ dynamic output) async {
+ /*String|Matcher<List<int>>*/ dynamic output) async {
var packageConfig = PackageConfig([
Package('foo', Uri.file('/foo/'),
packageUriRoot: Uri.file('/foo/lib/'),
@@ -1177,12 +1183,13 @@
/// Expect that [testBuilder], given [assets], throws an
/// [InvalidMockitoAnnotationException] with a message containing [message].
-void expectBuilderThrows(
- {@required Map<String, String> assets, @required String message}) {
+void _expectBuilderThrows(
+ {@required Map<String, String> assets,
+ @required dynamic /*String|Matcher<List<int>>*/ message}) {
expect(
() async => await testBuilder(buildMocks(BuilderOptions({})), assets),
throwsA(TypeMatcher<InvalidMockitoAnnotationException>()
- .having((e) => e.message, 'message', contains(message))));
+ .having((e) => e.message, 'message', message)));
}
/// Dedent [input], so that each line is shifted to the left, so that the first