GitHub Sync (#602)
* Added support for generated mocks from typedef-aliased classes.
PiperOrigin-RevId: 501338177
* Update the mockito readme file.
PiperOrigin-RevId: 503229973
* Ignore an upcoming diagnostic for `operator ==`.
The comment on the `operator ==` override explains
why it is needed.
PiperOrigin-RevId: 503381552
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 9523511..cf8e110 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -2,7 +2,7 @@
# See https://docs.github.com/en/code-security/dependabot/dependabot-version-updates
version: 2
-updates:
+updates:
- package-ecosystem: github-actions
directory: /
schedule:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7423fc3..42496d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
* Fix nice mocks generation in mixed mode (generated code is pre null-safety,
while mocked class is null-safe).
* Require Dart >= 2.17.0.
+* Support typedef-aliased classes in `@GenerateMocks` and `@GenerateNiceMocks`
## 5.3.2
diff --git a/README.md b/README.md
index 47d394f..a658dbb 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,9 @@
-# mockito
+[![Dart CI](https://github.com/dart-lang/mockito/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/mockito/actions/workflows/test-package.yml)
+[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito)
+[![package publisher](https://img.shields.io/pub/publisher/mockito.svg)](https://pub.dev/packages/mockito/publisher)
Mock library for Dart inspired by [Mockito](https://github.com/mockito/mockito).
-[![Pub](https://img.shields.io/pub/v/mockito.svg)](https://pub.dev/packages/mockito)
-[![Build Status](https://travis-ci.org/dart-lang/mockito.svg?branch=master)](https://travis-ci.org/dart-lang/mockito)
-
## Let's create mocks
Mockito 5.0.0 supports Dart's new **null safety** language feature in Dart 2.12,
@@ -330,7 +329,7 @@
}
```
-The `Cat.sound` method retuns a non-nullable String, but no stub has been made
+The `Cat.sound` method returns a non-nullable String, but no stub has been made
with `when(cat.sound())`, so what should the code do? What is the "missing stub"
behavior?
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index 7735a9f..422be12 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -158,6 +158,7 @@
seenTypes.add(type);
librariesWithTypes.add(type.element.library);
type.element.accept(typeVisitor);
+ if (type.alias != null) type.alias!.element.accept(typeVisitor);
// For a type like `Foo<Bar>`, add the `Bar`.
type.typeArguments
.whereType<analyzer.InterfaceType>()
@@ -296,6 +297,12 @@
super.visitTypeParameterElement(element);
}
+ @override
+ void visitTypeAliasElement(TypeAliasElement element) {
+ _elements.add(element);
+ super.visitTypeAliasElement(element);
+ }
+
/// Adds [type] to the collected [_elements].
void _addType(analyzer.DartType? type) {
if (type == null) return;
@@ -406,14 +413,15 @@
final bool hasExplicitTypeArguments;
_MockTarget(
- this.classType,
- this.mockName, {
+ this.classType, {
required this.mixins,
required this.onMissingStub,
required this.unsupportedMembers,
required this.fallbackGenerators,
this.hasExplicitTypeArguments = false,
- });
+ String? mockName,
+ }) : mockName = mockName ??
+ 'Mock${classType.alias?.element.name ?? classType.element.name}';
InterfaceElement get interfaceElement => classType.element;
}
@@ -509,17 +517,17 @@
throw InvalidMockitoAnnotationException(
'Mockito cannot mock `dynamic`');
}
- final type = _determineDartType(typeToMock, entryLib.typeProvider);
- // For a generic class like `Foo<T>` or `Foo<T extends num>`, a type
- // literal (`Foo`) cannot express type arguments. The type argument(s) on
- // `type` have been instantiated to bounds here. Switch to the
- // declaration, which will be an uninstantiated type.
- final declarationType =
- (type.element.declaration as InterfaceElement).thisType;
- final mockName = 'Mock${declarationType.element.name}';
+ var type = _determineDartType(typeToMock, entryLib.typeProvider);
+ if (type.alias == null) {
+ // For a generic class without an alias like `Foo<T>` or
+ // `Foo<T extends num>`, a type literal (`Foo`) cannot express type
+ // arguments. The type argument(s) on `type` have been instantiated to
+ // bounds here. Switch to the declaration, which will be an
+ // uninstantiated type.
+ type = (type.element.declaration as InterfaceElement).thisType;
+ }
mockTargets.add(_MockTarget(
- declarationType,
- mockName,
+ type,
mixins: [],
onMissingStub: OnMissingStub.throwException,
unsupportedMembers: {},
@@ -562,35 +570,41 @@
}
}
var type = _determineDartType(typeToMock, entryLib.typeProvider);
-
final mockTypeArguments = mockType?.typeArguments;
- if (mockTypeArguments == null) {
- // The type was given without explicit type arguments. In
- // this case the type argument(s) on `type` have been instantiated to
- // bounds. Switch to the declaration, which will be an uninstantiated
- // type.
- type = (type.element.declaration as InterfaceElement).thisType;
- } else {
+ if (mockTypeArguments != null) {
+ final typeName =
+ type.alias?.element.getDisplayString(withNullability: false) ??
+ 'type $type';
+ final typeArguments = type.alias?.typeArguments ?? type.typeArguments;
// Check explicit type arguments for unknown types that were
// turned into `dynamic` by the analyzer.
- type.typeArguments.forEachIndexed((typeArgIdx, typeArgument) {
+ typeArguments.forEachIndexed((typeArgIdx, typeArgument) {
if (!typeArgument.isDynamic) return;
if (typeArgIdx >= mockTypeArguments.arguments.length) return;
final typeArgAst = mockTypeArguments.arguments[typeArgIdx];
if (typeArgAst is! ast.NamedType) {
// Is this even possible?
throw InvalidMockitoAnnotationException(
- 'Undefined type $typeArgAst passed as the ${(typeArgIdx + 1).ordinal} type argument for mocked type $type');
+ 'Undefined type $typeArgAst passed as the '
+ '${(typeArgIdx + 1).ordinal} type argument for mocked '
+ '$typeName.');
}
if (typeArgAst.name.name == 'dynamic') return;
throw InvalidMockitoAnnotationException(
- 'Undefined type $typeArgAst passed as the ${(typeArgIdx + 1).ordinal} type argument for mocked type $type. '
- 'Are you trying to pass to-be-generated mock class as a type argument? Mockito does not support that (yet).',
+ 'Undefined type $typeArgAst passed as the '
+ '${(typeArgIdx + 1).ordinal} type argument for mocked $typeName. Are '
+ 'you trying to pass to-be-generated mock class as a type argument? '
+ 'Mockito does not support that (yet).',
);
});
+ } else if (type.alias == null) {
+ // The mock type was given without explicit type arguments. In this case
+ // the type argument(s) on `type` have been instantiated to bounds if this
+ // isn't a type alias. Switch to the declaration, which will be an
+ // uninstantiated type.
+ type = (type.element.declaration as InterfaceElement).thisType;
}
- final mockName = mockSpec.getField('mockName')!.toSymbolValue() ??
- 'Mock${type.element.name}';
+ final mockName = mockSpec.getField('mockName')!.toSymbolValue();
final mixins = <analyzer.InterfaceType>[];
for (final m in mockSpec.getField('mixins')!.toListValue()!) {
final typeToMixin = m.toTypeValue();
@@ -662,7 +676,7 @@
mockSpec.getField('fallbackGenerators')!.toMapValue()!;
return _MockTarget(
type,
- mockName,
+ mockName: mockName,
mixins: mixins,
onMissingStub: onMissingStub,
unsupportedMembers: unsupportedMembers,
@@ -740,17 +754,17 @@
"'${elementToMock.displayName}' for the following reasons:\n"
'$joinedMessages');
}
+ if (typeToMock.alias != null &&
+ typeToMock.nullabilitySuffix == NullabilitySuffix.question) {
+ throw InvalidMockitoAnnotationException(
+ 'Mockito cannot mock a type-aliased nullable type: '
+ '${typeToMock.alias!.element.name}');
+ }
return typeToMock;
}
- var aliasElement = typeToMock.alias?.element;
- if (aliasElement != null) {
- throw InvalidMockitoAnnotationException('Mockito cannot mock a typedef: '
- '${aliasElement.displayName}');
- } else {
- throw InvalidMockitoAnnotationException(
- 'Mockito cannot mock a non-class: $typeToMock');
- }
+ throw InvalidMockitoAnnotationException('Mockito cannot mock a non-class: '
+ '${typeToMock.alias?.element.name ?? typeToMock.toString()}');
}
void _checkClassesToMockAreValid() {
@@ -1097,10 +1111,14 @@
});
Class _buildMockClass() {
- final typeToMock = mockTarget.classType;
+ final typeAlias = mockTarget.classType.alias;
+ final aliasedElement = typeAlias?.element;
+ final aliasedType =
+ typeAlias?.element.aliasedType as analyzer.InterfaceType?;
+ final typeToMock = aliasedType ?? mockTarget.classType;
final classToMock = mockTarget.interfaceElement;
final classIsImmutable = classToMock.metadata.any((it) => it.isImmutable);
- final className = classToMock.name;
+ final className = aliasedElement?.name ?? classToMock.name;
return Class((cBuilder) {
cBuilder
@@ -1118,25 +1136,32 @@
// For each type parameter on [classToMock], the Mock class needs a type
// parameter with same type variables, and a mirrored type argument for
// the "implements" clause.
- var typeArguments = <Reference>[];
+ final typeReferences = <Reference>[];
+
+ final typeParameters =
+ aliasedElement?.typeParameters ?? classToMock.typeParameters;
+ final typeArguments =
+ typeAlias?.typeArguments ?? typeToMock.typeArguments;
+
if (mockTarget.hasExplicitTypeArguments) {
// [typeToMock] is a reference to a type with type arguments (for
// example: `Foo<int>`). Generate a non-generic mock class which
// implements the mock target with said type arguments. For example:
// `class MockFoo extends Mock implements Foo<int> {}`
- for (var typeArgument in typeToMock.typeArguments) {
- typeArguments.add(_typeReference(typeArgument));
+ for (var typeArgument in typeArguments) {
+ typeReferences.add(_typeReference(typeArgument));
}
} else {
// [typeToMock] is a simple reference to a generic type (for example:
// `Foo`, a reference to `class Foo<T> {}`). Generate a generic mock
// class which perfectly mirrors the type parameters on [typeToMock],
// forwarding them to the "implements" clause.
- for (var typeParameter in classToMock.typeParameters) {
+ for (var typeParameter in typeParameters) {
cBuilder.types.add(_typeParameterReference(typeParameter));
- typeArguments.add(refer(typeParameter.name));
+ typeReferences.add(refer(typeParameter.name));
}
}
+
for (final mixin in mockTarget.mixins) {
cBuilder.mixins.add(TypeReference((b) {
b
@@ -1147,15 +1172,21 @@
}
cBuilder.implements.add(TypeReference((b) {
b
- ..symbol = classToMock.name
- ..url = _typeImport(mockTarget.interfaceElement)
- ..types.addAll(typeArguments);
+ ..symbol = className
+ ..url = _typeImport(aliasedElement ?? classToMock)
+ ..types.addAll(typeReferences);
}));
if (mockTarget.onMissingStub == OnMissingStub.throwException) {
cBuilder.constructors.add(_constructorWithThrowOnMissingStub);
}
- final substitution = Substitution.fromInterfaceType(typeToMock);
+ final substitution = Substitution.fromPairs([
+ ...classToMock.typeParameters,
+ ...?aliasedElement?.typeParameters,
+ ], [
+ ...typeToMock.typeArguments,
+ ...?typeAlias?.typeArguments,
+ ]);
final members =
inheritanceManager.getInterface(classToMock).map.values.map((member) {
return ExecutableMember.from2(member, substitution);
diff --git a/lib/src/mock.dart b/lib/src/mock.dart
index f4b52d2..7fc4833 100644
--- a/lib/src/mock.dart
+++ b/lib/src/mock.dart
@@ -234,6 +234,7 @@
// make Analyzer happy, if we fake classes that override `==` to
// accept `Object?` or `dynamic` (most notably [Interceptor]).
@override
+ // ignore: non_nullable_equals_parameter
bool operator ==(Object? other) => identical(this, other);
@override
diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart
index 9162f5f..fcfcca0 100644
--- a/test/builder/auto_mocks_test.dart
+++ b/test/builder/auto_mocks_test.dart
@@ -118,11 +118,15 @@
var packageConfig = PackageConfig([
Package('foo', Uri.file('/foo/'),
packageUriRoot: Uri.file('/foo/lib/'),
- languageVersion: LanguageVersion(2, 12))
+ languageVersion: LanguageVersion(2, 13))
]);
- await testBuilder(buildMocks(BuilderOptions({})), sourceAssets,
- writer: writer, packageConfig: packageConfig);
+ await withEnabledExperiments(
+ () async => await testBuilder(
+ buildMocks(BuilderOptions({})), sourceAssets,
+ writer: writer, packageConfig: packageConfig),
+ ['nonfunction-type-aliases'],
+ );
var mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart');
return utf8.decode(writer.assets[mocksAsset]!);
}
@@ -1653,7 +1657,7 @@
void m<T>(
T? a,
T? b,
- ) =>
+ ) =>
''')),
);
});
@@ -2069,7 +2073,7 @@
int get m => (super.noSuchMethod(
Invocation.getter(#m),
returnValue: 0,
- ) as int);
+ ) as int);
'''), dedent2('''
set m(int? _m) => super.noSuchMethod(
Invocation.setter(
@@ -2156,7 +2160,7 @@
[other],
),
returnValue: 0,
- ) as int);
+ ) as int);
''')),
);
});
@@ -3240,7 +3244,7 @@
);
});
- test('throws when GenerateMocks references a typedef', () async {
+ test('throws when GenerateMocks references a function typedef', () async {
_expectBuilderThrows(
assets: {
...annotationsAsset,
@@ -3249,7 +3253,7 @@
typedef Foo = void Function();
'''),
},
- message: 'Mockito cannot mock a typedef: Foo',
+ message: 'Mockito cannot mock a non-class: Foo',
);
});
@@ -3366,6 +3370,178 @@
_containsAllOf('// ignore: must_be_immutable\nclass MockFoo'),
);
});
+
+ group('typedef mocks', () {
+ group('are generated properly', () {
+ test('when aliased type parameters are instantiated', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<T> {}
+ typedef Bar = Foo<int>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateMocks([Bar])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar'));
+ });
+
+ test('when no aliased type parameters are instantiated', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<T> {}
+ typedef Bar = Foo;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateMocks([Bar])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar'));
+ });
+
+ test('when the aliased type has no type parameters', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {}
+ typedef Bar = Foo;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateMocks([Bar])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar'));
+ });
+
+ test('when the typedef defines a type', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A, B> {}
+ typedef Bar<X> = Foo<int, X>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateMocks([Bar])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar<X> extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar<X>'));
+ });
+
+ test('when the typedef defines a bounded type', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A> {}
+ typedef Bar<X extends num> = Foo<X>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateMocks([Bar])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent,
+ contains('class MockBar<X extends num> extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar<X>'));
+ });
+
+ test('when the aliased type is a mixin', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ mixin Foo {
+ String get value;
+ }
+
+ typedef Bar = Foo;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+
+ @GenerateMocks([Bar])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar'));
+ expect(mocksContent, contains('String get value'));
+ });
+
+ test('when the aliased type is another typedef', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {}
+
+ typedef Bar = Foo;
+ typedef Baz = Bar;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+
+ @GenerateMocks([Baz])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBaz extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Baz'));
+ });
+ });
+
+ test('generation throws when the aliased type is nullable', () {
+ _expectBuilderThrows(
+ assets: {
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {
+ T get value;
+ }
+
+ typedef Bar = Foo?;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+
+ @GenerateMocks([Bar])
+ void main() {}
+ '''
+ },
+ message:
+ contains('Mockito cannot mock a type-aliased nullable type: Bar'),
+ enabledExperiments: ['nonfunction-type-aliases'],
+ languageVersion: LanguageVersion(2, 13),
+ );
+ });
+ });
}
TypeMatcher<List<int>> _containsAllOf(a, [b]) => decodedMatches(
@@ -3377,16 +3553,24 @@
void _expectBuilderThrows({
required Map<String, String> assets,
required dynamic /*String|Matcher<List<int>>*/ message,
+ List<String> enabledExperiments = const [],
+ LanguageVersion? languageVersion,
}) {
var packageConfig = PackageConfig([
Package('foo', Uri.file('/foo/'),
packageUriRoot: Uri.file('/foo/lib/'),
- languageVersion: LanguageVersion(2, 12))
+ languageVersion: languageVersion ?? LanguageVersion(2, 12))
]);
expect(
- () async => await testBuilder(buildMocks(BuilderOptions({})), assets,
- packageConfig: packageConfig),
+ () => withEnabledExperiments(
+ () => testBuilder(
+ buildMocks(BuilderOptions({})),
+ assets,
+ packageConfig: packageConfig,
+ ),
+ enabledExperiments,
+ ),
throwsA(TypeMatcher<InvalidMockitoAnnotationException>()
.having((e) => e.message, 'message', message)));
}
diff --git a/test/builder/custom_mocks_test.dart b/test/builder/custom_mocks_test.dart
index daae016..4db4961 100644
--- a/test/builder/custom_mocks_test.dart
+++ b/test/builder/custom_mocks_test.dart
@@ -1137,7 +1137,7 @@
);
});
- test('throws when MockSpec references a typedef', () async {
+ test('throws when MockSpec references a function typedef', () async {
_expectBuilderThrows(
assets: {
...annotationsAsset,
@@ -1146,7 +1146,7 @@
typedef Foo = void Function();
'''),
},
- message: 'Mockito cannot mock a typedef: Foo',
+ message: 'Mockito cannot mock a non-class: Foo',
);
});
@@ -1377,6 +1377,299 @@
expect(mocksContent, contains('class MockFoo'));
expect(mocksContent, contains('class MockBar'));
});
+
+ group('typedef mocks', () {
+ group('are generated properly', () {
+ test('when all aliased type parameters are instantiated', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A, B, C> {}
+ typedef Bar = Foo<int, bool, String>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateNiceMocks([
+ MockSpec<Bar>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar'));
+ });
+
+ test('when no aliased type parameters are instantiated', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<T> {}
+ typedef Bar = Foo;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateNiceMocks([
+ MockSpec<Bar>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar'));
+ });
+
+ test(
+ 'when the typedef defines a type and it corresponds to a different '
+ 'index of the aliased type', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A> {}
+ typedef Bar<X> = Foo<num, X>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateNiceMocks([
+ MockSpec<Bar<int>>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar<int>'));
+ });
+
+ test('when the aliased type has no type parameters', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {}
+ typedef Bar = Foo;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateNiceMocks([
+ MockSpec<Bar>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar'));
+ });
+
+ test('when the mock instantiates another typedef', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A> {}
+ typedef Bar<B> = Foo<B>;
+
+ class Baz<X> {}
+ typedef Qux<Y> = Baz<Y>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateNiceMocks([
+ MockSpec<Qux<Bar<int>>>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockQux extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Qux<_i2.Foo<int>>'));
+ });
+
+ test(
+ 'when the typedef defines a bounded class type and it is NOT '
+ 'instantiated', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A> {}
+ class Bar {}
+ typedef Baz<X extends Bar> = Foo<X>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateNiceMocks([
+ MockSpec<Baz>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent,
+ contains('class MockBaz<X extends _i1.Bar> extends _i2.Mock'));
+ expect(mocksContent, contains('implements _i1.Baz<X>'));
+ });
+
+ test(
+ 'when the typedef defines a bounded type and the mock instantiates '
+ 'it', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A> {}
+ typedef Bar<X extends num> = Foo<X>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateNiceMocks([
+ MockSpec<Bar<int>>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar<int>'));
+ });
+
+ test('when the aliased type has a parameterized method', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A> {
+ A get value;
+ }
+ '''),
+ 'bar|lib/bar.dart': dedent(r'''
+ import 'package:foo/foo.dart';
+ typedef Bar = Foo<String>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:bar/bar.dart';
+ import 'package:mockito/annotations.dart';
+ @GenerateNiceMocks([
+ MockSpec<Bar>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('String get value'));
+ });
+
+ test(
+ 'when the typedef is parameterized and the aliased type has a '
+ 'parameterized method', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo<A> {
+ A get value;
+ }
+ '''),
+ 'bar|lib/bar.dart': dedent(r'''
+ import 'package:foo/foo.dart';
+ typedef Bar<T> = Foo<T>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:bar/bar.dart';
+ import 'package:mockito/annotations.dart';
+
+ X fallbackGenerator<X>() {
+ throw 'unknown';
+ }
+
+ @GenerateNiceMocks([
+ MockSpec<Bar>(fallbackGenerators: {#value: fallbackGenerator}),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('T get value'));
+ });
+
+ test('when the aliased type is a mixin', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ mixin Foo {
+ String get value;
+ }
+
+ typedef Bar = Foo<String>;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+
+ @GenerateNiceMocks([
+ MockSpec<Bar>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBar extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Bar'));
+ expect(mocksContent, contains('String get value'));
+ });
+
+ test('when the aliased type is another typedef', () async {
+ final mocksContent = await buildWithNonNullable({
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {}
+
+ typedef Bar = Foo;
+ typedef Baz = Bar;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+
+ @GenerateNiceMocks([
+ MockSpec<Baz>(),
+ ])
+ void main() {}
+ '''
+ });
+
+ expect(mocksContent, contains('class MockBaz extends _i1.Mock'));
+ expect(mocksContent, contains('implements _i2.Baz'));
+ });
+ });
+
+ test('generation throws when the aliased type is nullable', () {
+ _expectBuilderThrows(
+ assets: {
+ ...annotationsAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {
+ T get value;
+ }
+
+ typedef Bar = Foo?;
+ '''),
+ 'foo|test/foo_test.dart': '''
+ import 'package:foo/foo.dart';
+ import 'package:mockito/annotations.dart';
+
+ @GenerateNiceMocks([MockSpec<Bar>()])
+ void main() {}
+ '''
+ },
+ message:
+ contains('Mockito cannot mock a type-aliased nullable type: Bar'),
+ );
+ });
+ });
}
TypeMatcher<List<int>> _containsAllOf(a, [b]) => decodedMatches(