Skip to content

Commit

Permalink
Override toString implementation on generated Fakes in order to mat…
Browse files Browse the repository at this point in the history
…ch the

signature of an overriding method which adds optional parameters.

Fixes #371

PiperOrigin-RevId: 377388828
  • Loading branch information
srawlins committed Jun 4, 2021
1 parent ed61909 commit 28395ca
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 34 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
72 changes: 42 additions & 30 deletions lib/src/builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1161,41 +1159,20 @@ 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 = <Reference>[];
var elementToFake = dartType.element;
final elementToFake = dartType.element;
if (elementToFake.isEnum) {
return _typeReference(dartType).property(
elementToFake.fields.firstWhere((f) => f.isEnumConstant).name);
} else {
// 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
Expand All @@ -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 = <Reference>[];
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
Expand Down
2 changes: 1 addition & 1 deletion lib/src/version.dart
Original file line number Diff line number Diff line change
@@ -1 +1 @@
const packageVersion = '5.0.9';
const packageVersion = '5.0.10';
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
20 changes: 20 additions & 0 deletions test/builder/auto_mocks_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 28395ca

Please sign in to comment.