From 28395ca617bce951502a4c357e0a8e4ccde76c29 Mon Sep 17 00:00:00 2001 From: srawlins Date: Thu, 3 Jun 2021 18:40:35 -0400 Subject: [PATCH] Override `toString` implementation on generated Fakes in order to match the signature of an overriding method which adds optional parameters. Fixes https://github.com/dart-lang/mockito/issues/371 PiperOrigin-RevId: 377388828 --- CHANGELOG.md | 5 ++- lib/src/builder.dart | 72 ++++++++++++++++++------------- lib/src/version.dart | 2 +- pubspec.yaml | 4 +- test/builder/auto_mocks_test.dart | 20 +++++++++ 5 files changed, 69 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9881152..7554929f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ -## 5.0.10-dev +## 5.0.10 * Generate a proper mock class when the mocked class overrides `toString`, `hashCode`, or `operator==`. [#420](https://github.com/dart-lang/mockito/issues/420) +* Override `toString` implementation on generated Fakes in order to match the + signature of an overriding method which adds optional parameters. + [#371](https://github.com/dart-lang/mockito/issues/371) ## 5.0.9 diff --git a/lib/src/builder.dart b/lib/src/builder.dart index 1bc5918f..a4d64a92 100644 --- a/lib/src/builder.dart +++ b/lib/src/builder.dart @@ -907,8 +907,7 @@ class _MockLibraryInfo { if (_returnTypeIsNonNullable(method) || _hasNonNullableParameter(method) || _needsOverrideForVoidStub(method)) { - yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method, - className: type.getDisplayString(withNullability: true))); + yield Method((mBuilder) => _buildOverridingMethod(mBuilder, method)); } } if (type.mixins != null) { @@ -960,8 +959,7 @@ class _MockLibraryInfo { /// /// This new method just calls `super.noSuchMethod`, optionally passing a /// return value for methods with a non-nullable return type. - void _buildOverridingMethod(MethodBuilder builder, MethodElement method, - {required String className}) { + void _buildOverridingMethod(MethodBuilder builder, MethodElement method) { var name = method.displayName; if (method.isOperator) name = 'operator$name'; builder @@ -1161,11 +1159,7 @@ class _MockLibraryInfo { } Expression _dummyValueImplementing(analyzer.InterfaceType dartType) { - // For each type parameter on [dartType], the Mock class needs a type - // parameter with same type variables, and a mirrored type argument for the - // "implements" clause. - var typeParameters = []; - var elementToFake = dartType.element; + final elementToFake = dartType.element; if (elementToFake.isEnum) { return _typeReference(dartType).property( elementToFake.fields.firstWhere((f) => f.isEnumConstant).name); @@ -1173,29 +1167,12 @@ class _MockLibraryInfo { // There is a potential for these names to collide. If one mock class // requires a fake for a certain Foo, and another mock class requires a // fake for a different Foo, they will collide. - var fakeName = '_Fake${elementToFake.name}'; + final fakeName = '_Fake${elementToFake.name}'; // Only make one fake class for each class that needs to be faked. if (!fakedClassElements.contains(elementToFake)) { - fakeClasses.add(Class((cBuilder) { - cBuilder - ..name = fakeName - ..extend = referImported('Fake', 'package:mockito/mockito.dart'); - if (elementToFake.typeParameters != null) { - for (var typeParameter in elementToFake.typeParameters) { - cBuilder.types.add(_typeParameterReference(typeParameter)); - typeParameters.add(refer(typeParameter.name)); - } - } - cBuilder.implements.add(TypeReference((b) { - b - ..symbol = elementToFake.name - ..url = _typeImport(elementToFake) - ..types.addAll(typeParameters); - })); - })); - fakedClassElements.add(elementToFake); - } - var typeArguments = dartType.typeArguments; + _addFakeClass(fakeName, elementToFake); + } + final typeArguments = dartType.typeArguments; return TypeReference((b) { b ..symbol = fakeName @@ -1204,6 +1181,41 @@ class _MockLibraryInfo { } } + /// Adds a [Fake] implementation of [elementToFake], named [fakeName]. + void _addFakeClass(String fakeName, ClassElement elementToFake) { + fakeClasses.add(Class((cBuilder) { + // For each type parameter on [elementToFake], the Fake class needs a type + // parameter with same type variables, and a mirrored type argument for + // the "implements" clause. + final typeParameters = []; + cBuilder + ..name = fakeName + ..extend = referImported('Fake', 'package:mockito/mockito.dart'); + if (elementToFake.typeParameters != null) { + for (var typeParameter in elementToFake.typeParameters) { + cBuilder.types.add(_typeParameterReference(typeParameter)); + typeParameters.add(refer(typeParameter.name)); + } + } + cBuilder.implements.add(TypeReference((b) { + b + ..symbol = elementToFake.name + ..url = _typeImport(elementToFake) + ..types.addAll(typeParameters); + })); + + final toStringMethod = elementToFake.methods + .firstWhereOrNull((method) => method.name == 'toString'); + if (toStringMethod != null) { + // If [elementToFake] includes an overriding `toString` implementation, + // we need to include an implementation which matches the signature. + cBuilder.methods.add(Method( + (mBuilder) => _buildOverridingMethod(mBuilder, toStringMethod))); + } + })); + fakedClassElements.add(elementToFake); + } + /// Returns a [Parameter] which matches [parameter]. /// /// If [parameter] is unnamed (like a positional parameter in a function diff --git a/lib/src/version.dart b/lib/src/version.dart index e141d2dd..1b9e3c1a 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -1 +1 @@ -const packageVersion = '5.0.9'; +const packageVersion = '5.0.10'; diff --git a/pubspec.yaml b/pubspec.yaml index 6ef8609f..bb6f5678 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: mockito -version: 5.0.9 +version: 5.0.10 -description: A mock framework inspired by Mockito. With APIs for Fakes, Mocks, behavior verification, stubbing, and much more. +description: A mock framework inspired by Mockito. With APIs for Fakes, Mocks, behavior verification, stubbing, and much more. homepage: https://github.com/dart-lang/mockito environment: diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart index 4eddf527..5ff3693f 100644 --- a/test/builder/auto_mocks_test.dart +++ b/test/builder/auto_mocks_test.dart @@ -2224,6 +2224,26 @@ void main() { ); }); + test('generates a fake class with an overridden `toString` implementation', + () async { + await expectSingleNonNullableOutput( + dedent(''' + class Foo { + Bar m1() => Bar('name1'); + } + class Bar { + String toString({bool a = true}) => ''; + } + '''), + _containsAllOf(dedent(''' + class _FakeBar extends _i1.Fake implements _i2.Bar { + @override + String toString({bool? a = true}) => super.toString(); + } + ''')), + ); + }); + test('deduplicates fake classes', () async { var mocksContent = await buildWithSingleNonNullableSource(dedent(r''' class Foo {