MockBuilder: Fix two bugs regarding the GenerateMocks annotation:
* Generate mocks for all @GenerateMocks annotations on a top-level element. Previously only the first was respected.
* If a private class is listed in @GenerateMocks, throw at compile time.
PiperOrigin-RevId: 317729616
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index 24eb7a3..539042a 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -46,22 +46,25 @@
final objectsToMock = <DartObject>{};
for (final element in entryLib.topLevelElements) {
- final annotation = element.metadata.firstWhere(
- (annotation) =>
- annotation.element is ConstructorElement &&
- annotation.element.enclosingElement.name == 'GenerateMocks',
- orElse: () => null);
- 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 GenerateMocks "classes" argument is missing, includes an '
- 'unknown type, or includes an extension');
+ // TODO(srawlins): Re-think the idea of multiple @GenerateMocks
+ // annotations, on one element or even on different elements in a library.
+ for (final annotation in element.metadata) {
+ if (annotation == null) continue;
+ if (annotation.element is! ConstructorElement ||
+ annotation.element.enclosingElement.name != 'GenerateMocks') {
+ 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 GenerateMocks "classes" argument is missing, includes an '
+ 'unknown type, or includes an extension');
+ }
+ objectsToMock.addAll(classesField.toListValue());
}
- objectsToMock.addAll(classesField.toListValue());
}
var classesToMock =
@@ -121,6 +124,11 @@
'${elementToMock.displayName}. It is illegal to subtype this '
'type.');
}
+ if (elementToMock.isPrivate) {
+ throw InvalidMockitoAnnotationException(
+ 'The "classes" argument includes a private type: '
+ '${elementToMock.displayName}.');
+ }
classesToMock.add(typeToMock);
} else if (elementToMock is GenericFunctionTypeElement &&
elementToMock.enclosingElement is FunctionTypeAliasElement) {
diff --git a/test/builder_test.dart b/test/builder_test.dart
index aebddb3..41e1f3e 100644
--- a/test/builder_test.dart
+++ b/test/builder_test.dart
@@ -239,6 +239,32 @@
);
});
+ test('generates mock classes from multiple annotations on a single element',
+ () async {
+ await _testWithNonNullable(
+ {
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {}
+ class Bar {}
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateMocks([Foo])
+ @GenerateMocks([Bar])
+ void barTests() {}
+ '''
+ },
+ outputs: {
+ 'foo|test/foo_test.mocks.dart': _containsAllOf(
+ 'class MockFoo extends _i1.Mock implements _i2.Foo {}',
+ 'class MockBar extends _i1.Mock implements _i2.Bar {}',
+ ),
+ },
+ );
+ });
+
test('generates generic mock classes', () async {
await _expectSingleNonNullableOutput(
dedent(r'''
@@ -1242,6 +1268,23 @@
);
});
+ test('throws when GenerateMocks is given a private class', () async {
+ _expectBuilderThrows(
+ assets: {
+ ...annotationsAsset,
+ 'foo|test/foo_test.dart': dedent('''
+ import 'package:mockito/annotations.dart';
+ // Missing required argument to GenerateMocks.
+ @GenerateMocks([_Foo])
+ void main() {}
+ class _Foo {}
+ '''),
+ },
+ message:
+ contains('The "classes" argument includes a private type: _Foo.'),
+ );
+ });
+
test('throws when GenerateMocks references an unresolved type', () async {
_expectBuilderThrows(
assets: {