Refactor builder and add dummy return value feature:
* Instead of concatenating a pile of classes, use a Library;
this lets us start to import things properly.
* Refactor some code out of loops so it's more readable.
* Depending on return type of original methods, can generate
dummy return values.
* Test manually-written `super.noSuchMethod` calls which pass
a dummy return value. I don't know of any other code which
passes two arguments to noSuchMethod, so I want this tested
in all Dart environments.
PiperOrigin-RevId: 281056988
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a7b21e7..930be48 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+## 4.1.2
+
+* Introduce experimental code-generated mocks. This is primarily to support
+ the new "Non-nullable by default" (NNBD) type system coming soon to Dart.
+* Add an optional second parameter to `Mock.noSuchMethod`. This may break
+ clients who use the Mock class in unconventional ways, such as overriding
+ `noSuchMethod` on a class which extends Mock. To fix, or prepare such code,
+ add a second parameter to such overriding `noSuchMethod` declaration.
+
## 4.1.1
* Mark the unexported and accidentally public `setDefaultResponse` as
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index 55d7972..40652bc 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart';
@@ -38,67 +39,75 @@
final entryLib = await buildStep.inputLibrary;
final resolver = buildStep.resolver;
- final mockLibrary = buildStep.inputId.changeExtension('.mocks.dart');
+ final mockLibraryAsset = buildStep.inputId.changeExtension('.mocks.dart');
- final mockClasses = <Class>[];
-
- for (final element in entryLib.topLevelElements) {
- final annotation = element.metadata.firstWhere(
- (annotation) =>
- annotation.element is ConstructorElement &&
- annotation.element.enclosingElement.name == 'GenerateMocks',
- orElse: () => null);
- if (annotation == null) continue;
- final generateMocksValue = annotation.computeConstantValue();
- // TODO(srawlins): handle `generateMocksValue == null`?
- final classesToMock = generateMocksValue.getField('classes');
- if (classesToMock.isNull) {
- throw InvalidMockitoAnnotationException(
- 'The "classes" argument has unknown types');
- }
- for (final classToMock in classesToMock.toListValue()) {
- final dartTypeToMock = classToMock.toTypeValue();
- // TODO(srawlins): Import the library which declares [dartTypeToMock].
- // TODO(srawlins): Import all supporting libraries, used in type
- // signatures.
- if (dartTypeToMock == null) {
+ final mockLibrary = Library((lBuilder) {
+ for (final element in entryLib.topLevelElements) {
+ final annotation = element.metadata.firstWhere(
+ (annotation) =>
+ annotation.element is ConstructorElement &&
+ annotation.element.enclosingElement.name == 'GenerateMocks',
+ orElse: () => null);
+ if (annotation == null) continue;
+ final generateMocksValue = annotation.computeConstantValue();
+ // TODO(srawlins): handle `generateMocksValue == null`?
+ final classesToMock = generateMocksValue.getField('classes');
+ if (classesToMock.isNull) {
throw InvalidMockitoAnnotationException(
- 'The "classes" argument includes a non-type: $classToMock');
+ 'The "classes" argument has unknown types');
}
- final elementToMock = dartTypeToMock.element;
- if (elementToMock is ClassElement) {
- if (elementToMock.isEnum) {
- throw InvalidMockitoAnnotationException(
- 'The "classes" argument includes an enum: '
- '${elementToMock.displayName}');
- }
- // TODO(srawlins): Catch when someone tries to generate mocks for an
- // un-subtypable class, like bool, String, FutureOr, etc.
- mockClasses.add(_buildCodeForClass(dartTypeToMock, elementToMock));
- } else if (elementToMock is GenericFunctionTypeElement &&
- elementToMock.enclosingElement is FunctionTypeAliasElement) {
- throw InvalidMockitoAnnotationException(
- 'The "classes" argument includes a typedef: '
- '${elementToMock.enclosingElement.displayName}');
- } else {
- throw InvalidMockitoAnnotationException(
- 'The "classes" argument includes a non-class: '
- '${elementToMock.displayName}');
- }
+ _buildMockClasses(classesToMock.toListValue(), lBuilder);
}
- }
+ });
- if (mockClasses.isEmpty) {
+ if (mockLibrary.body.isEmpty) {
// Nothing to mock here!
return;
}
- final emitter = DartEmitter();
- final mockLibraryContent = DartFormatter()
- .format(mockClasses.map((c) => c.accept(emitter)).join('\n'));
+ final emitter = DartEmitter.scoped();
+ final mockLibraryContent =
+ DartFormatter().format(mockLibrary.accept(emitter).toString());
- await buildStep.writeAsString(mockLibrary, mockLibraryContent);
+ await buildStep.writeAsString(mockLibraryAsset, mockLibraryContent);
+ }
+
+ /// Build mock classes for [classesToMock], a list of classes obtained from a
+ /// `@GenerateMocks` annotation.
+ void _buildMockClasses(
+ List<DartObject> classesToMock, LibraryBuilder lBuilder) {
+ for (final classToMock in classesToMock) {
+ final dartTypeToMock = classToMock.toTypeValue();
+ // TODO(srawlins): Import the library which declares [dartTypeToMock].
+ // TODO(srawlins): Import all supporting libraries, used in type
+ // signatures.
+ if (dartTypeToMock == null) {
+ throw InvalidMockitoAnnotationException(
+ 'The "classes" argument includes a non-type: $classToMock');
+ }
+
+ final elementToMock = dartTypeToMock.element;
+ if (elementToMock is ClassElement) {
+ if (elementToMock.isEnum) {
+ throw InvalidMockitoAnnotationException(
+ 'The "classes" argument includes an enum: '
+ '${elementToMock.displayName}');
+ }
+ // TODO(srawlins): Catch when someone tries to generate mocks for an
+ // un-subtypable class, like bool, String, FutureOr, etc.
+ lBuilder.body.add(_buildCodeForClass(dartTypeToMock, elementToMock));
+ } else if (elementToMock is GenericFunctionTypeElement &&
+ elementToMock.enclosingElement is FunctionTypeAliasElement) {
+ throw InvalidMockitoAnnotationException(
+ 'The "classes" argument includes a typedef: '
+ '${elementToMock.enclosingElement.displayName}');
+ } else {
+ throw InvalidMockitoAnnotationException(
+ 'The "classes" argument includes a non-class: '
+ '${elementToMock.displayName}');
+ }
+ }
}
Class _buildCodeForClass(DartType dartType, ClassElement classToMock) {
@@ -107,7 +116,8 @@
return Class((cBuilder) {
cBuilder
..name = 'Mock$className'
- ..extend = refer('Mock')
+ ..extend = refer('Mock', 'package:mockito/mockito.dart')
+ // TODO(srawlins): Add URI of dartType.
..implements.add(refer(className))
..docs.add('/// A class which mocks [$className].')
..docs.add('///')
@@ -125,15 +135,40 @@
}
}
for (final method in classToMock.methods) {
- if (method.parameters.isEmpty || method.isPrivate || method.isStatic) {
+ if (method.isPrivate || method.isStatic) {
continue;
}
- cBuilder.methods.add(
- Method((mBuilder) => _buildOverridingMethod(mBuilder, method)));
+ if (_returnTypeIsNonNullable(method) ||
+ _hasNonNullableParameter(method)) {
+ cBuilder.methods.add(
+ Method((mBuilder) => _buildOverridingMethod(mBuilder, method)));
+ }
}
});
}
+ // TODO(srawlins): Update this logic to correctly handle non-nullable return
+ // types. Right now this logic does not seem to be available on DartType.
+ bool _returnTypeIsNonNullable(MethodElement method) {
+ var type = method.returnType;
+ if (type.isDynamic || type.isVoid) return false;
+ if (method.isAsynchronous && type.isDartAsyncFuture ||
+ type.isDartAsyncFutureOr) {
+ var typeArgument = (type as InterfaceType).typeArguments.first;
+ if (typeArgument.isDynamic || typeArgument.isVoid) {
+ // An asynchronous method which returns `Future<void>`, for example,
+ // does not need a dummy return value.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // TODO(srawlins): Update this logic to correctly handle non-nullable return
+ // types. Right now this logic does not seem to be available on DartType.
+ bool _hasNonNullableParameter(MethodElement method) =>
+ method.parameters.isNotEmpty;
+
/// Build a method which overrides [method], with all non-nullable
/// parameter types widened to be nullable.
///
@@ -150,6 +185,10 @@
..name = method.displayName
..returns = refer(method.returnType.displayName);
+ if (method.typeParameters != null && method.typeParameters.isNotEmpty) {
+ builder.types.addAll(method.typeParameters.map((p) => refer(p.name)));
+ }
+
if (method.isAsynchronous) {
builder.modifier =
method.isGenerator ? MethodModifier.asyncStar : MethodModifier.async;
@@ -180,23 +219,60 @@
// type checks.
// TODO(srawlins): Handle getter invocations with `Invocation.getter`,
// and operators???
- // TODO(srawlins): Handle generic methods with `Invocation.genericMethod`.
final invocation = refer('Invocation').property('method').call([
refer('#${method.displayName}'),
literalList(invocationPositionalArgs),
if (invocationNamedArgs.isNotEmpty) literalMap(invocationNamedArgs),
]);
+ final noSuchMethodArgs = <Expression>[invocation];
+ if (_returnTypeIsNonNullable(method)) {
+ final dummyReturnValue = _dummyValue(method.returnType);
+ noSuchMethodArgs.add(dummyReturnValue);
+ }
final returnNoSuchMethod =
- refer('super').property('noSuchMethod').call([invocation]);
+ refer('super').property('noSuchMethod').call(noSuchMethodArgs);
builder.body = returnNoSuchMethod.code;
}
+ Expression _dummyValue(DartType type) {
+ if (type.isDartCoreBool) {
+ return literalFalse;
+ } else if (type.isDartCoreDouble) {
+ return literalNum(0.0);
+ } else if (type.isDartAsyncFuture || type.isDartAsyncFutureOr) {
+ var typeArgument = (type as InterfaceType).typeArguments.first;
+ return refer('Future')
+ .property('value')
+ .call([_dummyValue(typeArgument)]);
+ } else if (type.isDartCoreInt) {
+ return literalNum(0);
+ } else if (type.isDartCoreList) {
+ return literalList([]);
+ } else if (type.isDartCoreMap) {
+ return literalMap({});
+ } else if (type.isDartCoreNum) {
+ return literalNum(0);
+ } else if (type.isDartCoreSet) {
+ // This is perhaps a dangerous hack. The code, `{}`, is parsed as a Set
+ // literal if it is used in a context which explicitly expects a Set.
+ return literalMap({});
+ } else if (type.isDartCoreString) {
+ return literalString('');
+ } else {
+ // TODO(srawlins): Returning null for now, but really this should only
+ // ever get to a state where we have to make a Fake class which implements
+ // the type, and return a no-op constructor call to that Fake class here.
+ return literalNull;
+ }
+ }
+
/// Returns a [Parameter] which matches [parameter].
Parameter _matchingParameter(ParameterElement parameter) =>
Parameter((pBuilder) {
pBuilder
..name = parameter.displayName
+ // TODO(srawlins): Add URI of `parameter.type`.
..type = refer(parameter.type.displayName);
if (parameter.isNamed) pBuilder.named = true;
if (parameter.defaultValueCode != null) {
diff --git a/lib/src/mock.dart b/lib/src/mock.dart
index 320586e..4ec119c 100644
--- a/lib/src/mock.dart
+++ b/lib/src/mock.dart
@@ -107,20 +107,27 @@
@override
@visibleForTesting
- dynamic noSuchMethod(Invocation invocation) {
+
+ /// Handles method stubbing, method call verification, and real method calls.
+ ///
+ /// If passed, [returnValue] will be returned during method stubbing and
+ /// method call verification. This is useful in cases where the method
+ /// invocation which led to `noSuchMethod` being called has a non-nullable
+ /// return type.
+ dynamic noSuchMethod(Invocation invocation, [Object /*?*/ returnValue]) {
// noSuchMethod is that 'magic' that allows us to ignore implementing fields
// and methods and instead define them later at compile-time per instance.
// See "Emulating Functions and Interactions" on dartlang.org: goo.gl/r3IQUH
invocation = _useMatchedInvocationIfSet(invocation);
if (_whenInProgress) {
_whenCall = _WhenCall(this, invocation);
- return null;
+ return returnValue;
} else if (_verificationInProgress) {
_verifyCalls.add(_VerifyCall(this, invocation));
- return null;
+ return returnValue;
} else if (_untilCalledInProgress) {
_untilCall = _UntilCall(this, invocation);
- return null;
+ return returnValue;
} else {
_realCalls.add(RealCall(this, invocation));
_invocationStreamController.add(invocation);
diff --git a/test/builder_test.dart b/test/builder_test.dart
index 5c24898..cc7db04 100644
--- a/test/builder_test.dart
+++ b/test/builder_test.dart
@@ -50,7 +50,7 @@
...simpleTestAsset,
'foo|lib/foo.dart': dedent(r'''
class Foo {
- int a() => 7;
+ dynamic a() => 7;
int _b(int x) => 8;
static int c(int y) => 9;
}
@@ -58,10 +58,12 @@
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
- class MockFoo extends Mock implements Foo {}
+ class MockFoo extends _i1.Mock implements Foo {}
'''),
},
);
@@ -84,10 +86,12 @@
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
- class MockFoo extends Mock implements Foo {}
+ class MockFoo extends _i1.Mock implements Foo {}
'''),
},
);
@@ -112,10 +116,12 @@
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
- class MockFoo extends Mock implements Foo {
+ class MockFoo extends _i1.Mock implements Foo {
dynamic a(int m, String n) =>
super.noSuchMethod(Invocation.method(#a, [m, n]));
}
@@ -146,10 +152,12 @@
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
- class MockFoo extends Mock implements Foo {
+ class MockFoo extends _i1.Mock implements Foo {
dynamic a(int m, String n) =>
super.noSuchMethod(Invocation.method(#a, [m, n]));
dynamic b(List<int> list) =>
@@ -188,10 +196,12 @@
},
outputs: {
'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
- class MockFoo extends Mock implements Foo {
+ class MockFoo extends _i1.Mock implements Foo {
dynamic a(int m, String n) =>
super.noSuchMethod(Invocation.method(#a, [m, n]));
}
@@ -199,7 +209,7 @@
/// A class which mocks [Bar].
///
/// See the documentation for Mockito's code generation for more information.
- class MockBar extends Mock implements Bar {
+ class MockBar extends _i1.Mock implements Bar {
dynamic b(List<int> list) =>
super.noSuchMethod(Invocation.method(#b, [list]));
}
@@ -208,7 +218,39 @@
);
});
- test('generates a mock class and overrides getters and setters', () async {
+ test('overrides generic methods', () async {
+ await testBuilder(
+ buildMocks(BuilderOptions({})),
+ {
+ ...annotationsAsset,
+ ...simpleTestAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {
+ dynamic f<T>(int a) {}
+ // Bounded type parameters blocked by
+ // https://github.com/dart-lang/code_builder/issues/251.
+ // dynamic g<T extends Comparable>(int a) {}
+ }
+ '''),
+ },
+ outputs: {
+ // TODO(srawlins): The getter will appear when it has a non-nullable
+ // return type.
+ 'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
+ /// A class which mocks [Foo].
+ ///
+ /// See the documentation for Mockito's code generation for more information.
+ class MockFoo extends _i1.Mock implements Foo {
+ dynamic f<T>(int a) => super.noSuchMethod(Invocation.method(#f, [a]));
+ }
+ '''),
+ },
+ );
+ });
+
+ test('overrides getters and setters', () async {
await testBuilder(
buildMocks(BuilderOptions({})),
{
@@ -226,10 +268,12 @@
// TODO(srawlins): The getter will appear when it has a non-nullable
// return type.
'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
/// A class which mocks [Foo].
///
/// See the documentation for Mockito's code generation for more information.
- class MockFoo extends Mock implements Foo {
+ class MockFoo extends _i1.Mock implements Foo {
set b(int value) => super.noSuchMethod(Invocation.setter(#b, [value]));
}
'''),
@@ -237,6 +281,74 @@
);
});
+ test('creates dummy non-null return values for known core classes', () async {
+ await testBuilder(
+ buildMocks(BuilderOptions({})),
+ {
+ ...annotationsAsset,
+ ...simpleTestAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {
+ bool m1() => false;
+ double m2() => 3.14;
+ int m3() => 7;
+ String m4() => "Hello";
+ List<Foo> m5() => [Foo()];
+ Set<Foo> m6() => {Foo()};
+ Map<int, Foo> m7() => {7: Foo()};
+ }
+ '''),
+ },
+ outputs: {
+ 'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
+ /// A class which mocks [Foo].
+ ///
+ /// See the documentation for Mockito's code generation for more information.
+ class MockFoo extends _i1.Mock implements Foo {
+ bool m1() => super.noSuchMethod(Invocation.method(#m1, []), false);
+ double m2() => super.noSuchMethod(Invocation.method(#m2, []), 0.0);
+ int m3() => super.noSuchMethod(Invocation.method(#m3, []), 0);
+ String m4() => super.noSuchMethod(Invocation.method(#m4, []), '');
+ List<Foo> m5() => super.noSuchMethod(Invocation.method(#m5, []), []);
+ Set<Foo> m6() => super.noSuchMethod(Invocation.method(#m6, []), {});
+ Map<int, Foo> m7() => super.noSuchMethod(Invocation.method(#m7, []), {});
+ }
+ '''),
+ },
+ );
+ });
+
+ test('creates dummy non-null return values for Futures of known core classes',
+ () async {
+ await testBuilder(
+ buildMocks(BuilderOptions({})),
+ {
+ ...annotationsAsset,
+ ...simpleTestAsset,
+ 'foo|lib/foo.dart': dedent(r'''
+ class Foo {
+ Future<bool> m1() async => false;
+ }
+ '''),
+ },
+ outputs: {
+ 'foo|test/foo_test.mocks.dart': dedent(r'''
+ import 'package:mockito/mockito.dart' as _i1;
+
+ /// A class which mocks [Foo].
+ ///
+ /// See the documentation for Mockito's code generation for more information.
+ class MockFoo extends _i1.Mock implements Foo {
+ Future<bool> m1() async =>
+ super.noSuchMethod(Invocation.method(#m1, []), Future.value(false));
+ }
+ '''),
+ },
+ );
+ });
+
test('throws when GenerateMocks references an unresolved type', () async {
expectBuilderThrows(
assets: {
diff --git a/test/mockito_test.dart b/test/mockito_test.dart
index 1b80150..c1f65e2 100644
--- a/test/mockito_test.dart
+++ b/test/mockito_test.dart
@@ -65,9 +65,6 @@
}
}
-String noMatchingCallsFooter = "(If you called `verify(...).called(0);`, "
- "please instead use `verifyNever(...);`.)";
-
void main() {
_MockedClass mock;
diff --git a/test/nnbd_support_test.dart b/test/nnbd_support_test.dart
new file mode 100644
index 0000000..ed36543
--- /dev/null
+++ b/test/nnbd_support_test.dart
@@ -0,0 +1,125 @@
+// Copyright 2019 Dart Mockito authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:mockito/mockito.dart';
+import 'package:test/test.dart';
+
+class Foo {
+ String /*?*/ returnsNullableString() => 'Hello';
+
+ // TODO(srawlins): When it becomes available, opt this test library into NNBD,
+ // and make this method really return a non-nullable String.
+ String /*!*/ returnsNonNullableString() => 'Hello';
+}
+
+class MockFoo extends Mock implements Foo {
+ String /*!*/ returnsNonNullableString() {
+ return super.noSuchMethod(
+ Invocation.method(#returnsNonNullableString, []), 'Dummy');
+ }
+}
+
+void main() {
+ MockFoo mock;
+
+ setUp(() {
+ mock = MockFoo();
+ });
+
+ tearDown(() {
+ // In some of the tests that expect an Error to be thrown, Mockito's
+ // global state can become invalid. Reset it.
+ resetMockitoState();
+ });
+
+ group('Using nSM out of the box,', () {
+ test('nSM returns the dummy value during method stubbing', () {
+ // Trigger method stubbing.
+ final whenCall = when;
+ final stubbedResponse = mock.returnsNullableString();
+ expect(stubbedResponse, equals(null));
+ whenCall(stubbedResponse).thenReturn('A');
+ });
+
+ test('nSM returns the dummy value during method call verification', () {
+ when(mock.returnsNullableString()).thenReturn('A');
+
+ // Make a real call.
+ final realResponse = mock.returnsNullableString();
+ expect(realResponse, equals('A'));
+
+ // Trigger method call verification.
+ final verifyCall = verify;
+ final verificationResponse = mock.returnsNullableString();
+ expect(verificationResponse, equals(null));
+ verifyCall(verificationResponse);
+ });
+
+ test(
+ 'nSM returns the dummy value during method call verification, using '
+ 'verifyNever', () {
+ // Trigger method call verification.
+ final verifyNeverCall = verifyNever;
+ final verificationResponse = mock.returnsNullableString();
+ expect(verificationResponse, equals(null));
+ verifyNeverCall(verificationResponse);
+ });
+ });
+
+ group('Using a method stub which passes a return argument to nSM,', () {
+ test('nSM returns the dummy value during method stubbing', () {
+ // Trigger method stubbing.
+ final whenCall = when;
+ final stubbedResponse = mock.returnsNonNullableString();
+
+ // Under the pre-NNBD type system, this expectation is not interesting.
+ // Under the NNBD type system, however, it is important that the second
+ // argument passed to `noSuchMethod` in `returnsNonNullableString` is
+ // returned by `noSuchMethod`, so that non-null values are can be returned
+ // when necessary.
+ expect(stubbedResponse, equals('Dummy'));
+ whenCall(stubbedResponse).thenReturn('A');
+ });
+
+ test('nSM returns the dummy value during method call verification', () {
+ when(mock.returnsNonNullableString()).thenReturn('A');
+
+ // Make a real call.
+ final realResponse = mock.returnsNonNullableString();
+
+ // Under the pre-NNBD type system, this expectation is not interesting.
+ // Under the NNBD type system, however, it is important that the second
+ // argument passed to `noSuchMethod` in `returnsNonNullableString` is
+ // _not_ returned by `noSuchMethod`; the canned response, `'A'`, should be
+ // returned.
+ expect(realResponse, equals('A'));
+
+ // Trigger method call verification.
+ final verifyCall = verify;
+ final verificationResponse = mock.returnsNonNullableString();
+ expect(verificationResponse, equals('Dummy'));
+ verifyCall(verificationResponse);
+ });
+
+ test(
+ 'nSM returns the dummy value during method call verification, using '
+ 'verifyNever', () {
+ // Trigger method call verification.
+ final verifyNeverCall = verifyNever;
+ final verificationResponse = mock.returnsNonNullableString();
+ expect(verificationResponse, equals('Dummy'));
+ verifyNeverCall(verificationResponse);
+ });
+ });
+}