Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions example/lib/src/notifiers/notifier/notifier.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class DummySource extends _$DummySource {
}

Future<void> someSideEffect() async {}

String get dummy => "Hello, world!";
}

@riverpod
Expand Down
4 changes: 3 additions & 1 deletion example/test/unit/notifier/notifier_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ void main() async {
() => MockDummySource(() async => 36),
)
]);
final mockDummySource = container.read(dummySourceProvider(6).notifier) as MockDummySource;
when(mockDummySource.dummy).thenReturn("Hello!");

// Run test
final result = await container.read(dummyProvider(6).future);

// Verify
expect(result, 36);
final mockDummySource = container.read(dummySourceProvider(6).notifier) as MockDummySource;
verify(mockDummySource.someSideEffect()).called(1);
expect(mockDummySource.dummy, "Hello!");
});
}
23 changes: 21 additions & 2 deletions lib/src/code_builders/fake_type_code_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class FakeTypeCodeBuilder {
.toList();
}

Code? getStubValue(
Code? getMethodStubValue(
FakeType fakeType,
String methodName,
String positionalArgs,
Expand All @@ -125,7 +125,7 @@ class FakeTypeCodeBuilder {
if (element == null) {
return null;
} else if (element case InterfaceElement(isFutureOrStream: true)) {
return getStubValue(
return getMethodStubValue(
fakeType.parameterTypes!.first, methodName, positionalArgs);
} else {
return Code.scope((allocate) {
Expand All @@ -139,6 +139,25 @@ class FakeTypeCodeBuilder {
}
}

Code? getGetterStubValue(
FakeType fakeType,
String methodName,
) {
final element = fakeType.element;
if (element == null) {
return null;
} else {
return Code.scope((allocate) {
final dummyInvocation =
allocate(refer("dummyValue", "package:mockito/src/dummies.dart"));
final typeParam = _allocateType(fakeType, allocate);
final positionalParams =
"this, Invocation.getter(#$methodName)";
return "$dummyInvocation<$typeParam>($positionalParams)";
});
}
}

String _allocateType(FakeType type, String Function(Reference) allocate) {
final parameterTypes = type.parameterTypes;
final topLevelTypeCode = allocate(refer(
Expand Down
242 changes: 143 additions & 99 deletions lib/src/code_builders/method_code_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,137 +14,181 @@ class MethodCodeBuilder {
final FakeTypeCodeBuilder _fakeTypeGenerator;
final TypeReferencer _typeReferencer;

const MethodCodeBuilder(
this._fakeTypeGenerator,
this._typeReferencer,
);
const MethodCodeBuilder(this._fakeTypeGenerator, this._typeReferencer);

List<Method> generateMethods(
ClassElement classElement, {
bool seedValueProvided = true,
}) {
final methodElements = classElement.methods;
final returnTypes =
methodElements.map((method) => method.returnType).toSet();
final getterElements = classElement.getters;
final returnTypes = <ExecutableElement>[
...methodElements,
...getterElements,
].map((method) => method.returnType).toSet();
final fakedTypes = _fakeTypeGenerator.generateFakeTypes(returnTypes);
return methodElements
.map((methodElement) => switch (methodElement.name) {
"build" => generateBuildMethod(
methodElement, seedValueProvided),
_ => generateMethod(methodElement, fakedTypes),
})
final methods = methodElements
.map(
(methodElement) => switch (methodElement.name) {
"build" => generateBuildMethod(methodElement, seedValueProvided),
_ => generateMethod(methodElement, fakedTypes),
},
)
.whereType<Method>()
.toList();
final propertyAccessors = getterElements
.map((getter) => generateGetter(getter, fakedTypes))
.whereType<Method>()
.toList();
return propertyAccessors + methods;
}

Method generateBuildMethod(
MethodElement method,
bool seedValueProvided,
Method? generateGetter(
PropertyAccessorElement propertyAccessor,
Set<FakeType> fakeTypes,
) {
final isFuture = method.returnType.isDartAsyncFuture ||
final localName = propertyAccessor.name;
if (localName == null) {
return null;
}
final returnType = propertyAccessor.returnType;
final fakedReturnType = fakeTypes.cast<FakeType?>().firstWhere(
(fakeType) => fakeType?.originalType == returnType,
orElse: () => null,
);
final stubValue = switch (fakedReturnType) {
null => null,
_ => _fakeTypeGenerator.getGetterStubValue(fakedReturnType, localName),
};
return Method((methodBuilder) {
methodBuilder
..name = localName
..type = MethodType.getter
..returns = _typeReferencer.obtainReferenceForType(
propertyAccessor.returnType,
)
..annotations.add(const CodeExpression(Code("override")))
..body = Block((blockBuilder) {
blockBuilder.addStaticCode(
"return noSuchMethod(Invocation.getter(#$localName)",
);
if (stubValue != null) {
blockBuilder
..addStaticCode(", returnValueForMissingStub: ")
..addCode(stubValue)
..addStaticCode(", returnValue: ")
..addCode(stubValue);
}
blockBuilder.addStaticCode(",);");
});
});
}

Method generateBuildMethod(MethodElement method, bool seedValueProvided) {
final isFuture =
method.returnType.isDartAsyncFuture ||
method.returnType.isDartAsyncFutureOr;
final isStream = method.returnType.isDartAsyncStream;
final positionalArgs =
method.formalParameters.where((p) => p.isPositional).toList();
final positionalArgs = method.formalParameters
.where((p) => p.isPositional)
.toList();
final positionalArgsCode = positionalArgs.map((arg) => arg.name).join(", ");
return Method(
(methodBuilder) {
methodBuilder
..name = "build"
..body = Block((blockBuilder) {
blockBuilder.addStaticCode(_keepAliveInsert);
if (isStream) {
blockBuilder.addStaticCode("yield* ");
} else {
blockBuilder.addStaticCode("return ");
}
return Method((methodBuilder) {
methodBuilder
..name = "build"
..body = Block((blockBuilder) {
blockBuilder.addStaticCode(_keepAliveInsert);
if (isStream) {
blockBuilder.addStaticCode("yield* ");
} else {
blockBuilder.addStaticCode("return ");
}
blockBuilder.addStaticCode(
"noSuchMethod(Invocation.method(#build, [$positionalArgsCode])",
);
if (seedValueProvided) {
blockBuilder.addStaticCode(
"noSuchMethod(Invocation.method(#build, [$positionalArgsCode])",
", returnValueForMissingStub: seedBuilder()",
);
if (seedValueProvided) {
blockBuilder.addStaticCode(
", returnValueForMissingStub: seedBuilder()",
);
}
blockBuilder.addStaticCode(");");
})
..copyParameters(
method: method,
obtainReferenceForType: (type) =>
_typeReferencer.obtainReferenceForType(type, true),
)
..annotations.add(const CodeExpression(Code("override")))
..returns = _typeReferencer.obtainReferenceForType(method.returnType);
if (isFuture) {
methodBuilder.modifier = MethodModifier.async;
} else if (isStream) {
methodBuilder.modifier = MethodModifier.asyncStar;
}
},
);
}
blockBuilder.addStaticCode(");");
})
..copyParameters(
method: method,
obtainReferenceForType: (type) =>
_typeReferencer.obtainReferenceForType(type, true),
)
..annotations.add(const CodeExpression(Code("override")))
..returns = _typeReferencer.obtainReferenceForType(method.returnType);
if (isFuture) {
methodBuilder.modifier = MethodModifier.async;
} else if (isStream) {
methodBuilder.modifier = MethodModifier.asyncStar;
}
});
}

Method? generateMethod(
MethodElement method,
Set<FakeType> fakeTypes,
) {
Method? generateMethod(MethodElement method, Set<FakeType> fakeTypes) {
if (!method.isPublic) {
return null;
}
final methodName = method.name;
if(methodName == null) {
if (methodName == null) {
return null;
}
final isFuture = method.returnType.isDartAsyncFuture ||
final isFuture =
method.returnType.isDartAsyncFuture ||
method.returnType.isDartAsyncFutureOr;
final isStream = method.returnType.isDartAsyncStream;
final positionalArgs =
method.formalParameters.where((p) => p.isPositional).toList();
final positionalArgs = method.formalParameters
.where((p) => p.isPositional)
.toList();
final positionalArgsCode = positionalArgs.map((arg) => arg.name).join(", ");
final returnType = method.returnType;
final fakedReturnType = fakeTypes.cast<FakeType?>().firstWhere(
(fakeType) => fakeType?.originalType == returnType,
orElse: () => null,
);
(fakeType) => fakeType?.originalType == returnType,
orElse: () => null,
);

final stubValue = fakedReturnType != null
? _fakeTypeGenerator.getStubValue(
fakedReturnType, methodName, positionalArgsCode)
: null;
return Method(
(methodBuilder) {
methodBuilder
..name = method.name
..body = Block((blockBuilder) {
if (isStream) {
blockBuilder.addStaticCode("yield* ");
} else {
blockBuilder.addStaticCode("return ");
}
blockBuilder.addStaticCode(
"noSuchMethod(Invocation.method(#${method.name}, [$positionalArgsCode])",
);
if (stubValue != null) {
blockBuilder.addStaticCode(", returnValueForMissingStub: ");
blockBuilder.addCode(stubValue);
blockBuilder.addStaticCode(", returnValue: ");
blockBuilder.addCode(stubValue);
}
blockBuilder.addStaticCode(",);");
})
..copyParameters(
method: method,
obtainReferenceForType: (type) =>
_typeReferencer.obtainReferenceForType(type, true),
? _fakeTypeGenerator.getMethodStubValue(
fakedReturnType,
methodName,
positionalArgsCode,
)
..annotations.add(const CodeExpression(Code("override")))
..returns = _typeReferencer.obtainReferenceForType(method.returnType);
if (isFuture) {
methodBuilder.modifier = MethodModifier.async;
} else if (isStream) {
methodBuilder.modifier = MethodModifier.asyncStar;
}
},
);
: null;
return Method((methodBuilder) {
methodBuilder
..name = method.name
..body = Block((blockBuilder) {
if (isStream) {
blockBuilder.addStaticCode("yield* ");
} else {
blockBuilder.addStaticCode("return ");
}
blockBuilder.addStaticCode(
"noSuchMethod(Invocation.method(#${method.name}, [$positionalArgsCode])",
);
if (stubValue != null) {
blockBuilder.addStaticCode(", returnValueForMissingStub: ");
blockBuilder.addCode(stubValue);
blockBuilder.addStaticCode(", returnValue: ");
blockBuilder.addCode(stubValue);
}
blockBuilder.addStaticCode(",);");
})
..copyParameters(
method: method,
obtainReferenceForType: (type) =>
_typeReferencer.obtainReferenceForType(type, true),
)
..annotations.add(const CodeExpression(Code("override")))
..returns = _typeReferencer.obtainReferenceForType(method.returnType);
if (isFuture) {
methodBuilder.modifier = MethodModifier.async;
} else if (isStream) {
methodBuilder.modifier = MethodModifier.asyncStar;
}
});
}
}
10 changes: 10 additions & 0 deletions test/golden/flumepod/notifier/expected.flumepod.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ class MockTestNotifier extends _i1.$Notifier<String>
}
}

@override
String get getter {
return noSuchMethod(
Invocation.getter(#getter),
returnValueForMissingStub:
_i5.dummyValue<String>(this, Invocation.getter(#getter)),
returnValue: _i5.dummyValue<String>(this, Invocation.getter(#getter)),
);
}

@override
String build() {
// keepAlive() used so mock instance with stubbed/real calls isn't disposed.
Expand Down
2 changes: 2 additions & 0 deletions test/golden/flumepod/notifier/input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ class TestNotifier extends _$TestNotifier {
Future<void> asyncSideEffect() async {
return;
}

String get getter => "value";
}
Loading
Loading