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(