Extend using run-time dummy values to Futures

If we have to create a `Future` of an unknown type, we have two
alternatives:

 1. Create a fake class implementing `Future`. This is guaranteed
    to fail if ever `await`ed, but works nice when setting
    expectations.
 2. Try to find a value for `T` at run-time and create a real
    future. That can always fail if there is no dummy for `T`.

This change tries to unite these approaches:
 - It adds `dummyValueOrNull` function that returns `null` instead
   of throwing.
 - If we need a value of type `Future<T>`, first try if we have a
   dummy value for `T`. If it works, go with the real future,
   otherwise fall back to faking.

PiperOrigin-RevId: 533386034
diff --git a/lib/src/builder.dart b/lib/src/builder.dart
index b94c689..7ac6562 100644
--- a/lib/src/builder.dart
+++ b/lib/src/builder.dart
@@ -1497,10 +1497,23 @@
       if (typeArgument is analyzer.TypeParameterType &&
           typeArgumentIsPotentiallyNonNullable) {
         // We cannot create a valid Future for this unknown, potentially
-        // non-nullable type, so we'll use a `_FakeFuture`, which will throw
+        // non-nullable type, so try creating a value at run-time and if
+        // that fails, we'll use a `_FakeFuture`, which will throw
         // if awaited.
         final futureType = typeProvider.futureType(typeArguments.first);
-        return _dummyValueImplementing(futureType, invocation);
+        return referImported('ifNotNull', 'package:mockito/src/dummies.dart')
+            .call([
+          referImported('dummyValueOrNull', 'package:mockito/src/dummies.dart')
+              .call([refer('this'), invocation], {},
+                  [_typeReference(typeArgument)]),
+          Method((b) => b
+            ..requiredParameters.add(Parameter((p) => p
+              ..type = _typeReference(typeArgument)
+              ..name = 'v'))
+            ..body = _futureReference(_typeReference(typeArgument))
+                .property('value')
+                .call([refer('v')]).code).closure
+        ]).ifNullThen(_dummyValueImplementing(futureType, invocation));
       } else {
         // Create a real Future with a legal value, via [Future.value].
         final futureValueArguments = typeArgumentIsPotentiallyNonNullable
diff --git a/lib/src/dummies.dart b/lib/src/dummies.dart
index 9bf2dd0..9c6d8ca 100644
--- a/lib/src/dummies.dart
+++ b/lib/src/dummies.dart
@@ -125,7 +125,7 @@
   Stream<Never>.empty(),
 ];
 
-T dummyValue<T>(Object parent, Invocation invocation) {
+T? dummyValueOrNull<T>(Object parent, Invocation invocation) {
   if (null is T) return null as T;
   final dummyBuilder = _dummyBuilders[T] ?? _defaultDummyBuilders[T];
   if (dummyBuilder != null) {
@@ -135,6 +135,12 @@
     if (value is DummyBuilder) value = value(parent, invocation);
     if (value is T) return value;
   }
+  return null;
+}
+
+T dummyValue<T>(Object parent, Invocation invocation) {
+  final value = dummyValueOrNull<T>(parent, invocation);
+  if (value is T) return value;
   throw MissingDummyValueError(T);
 }
 
@@ -153,3 +159,7 @@
 void resetDummyBuilders() {
   _dummyBuilders.clear();
 }
+
+// Helper function.
+R? ifNotNull<T, R>(T? value, R Function(T) action) =>
+    value != null ? action(value) : null;
diff --git a/test/builder/auto_mocks_test.dart b/test/builder/auto_mocks_test.dart
index 12cccf1..ff6e713 100644
--- a/test/builder/auto_mocks_test.dart
+++ b/test/builder/auto_mocks_test.dart
@@ -2310,7 +2310,7 @@
         Future<T> m<T>() async => false;
       }
       '''),
-      _containsAllOf('returnValue: _FakeFuture_0<T>('),
+      _containsAllOf('dummyValueOrNull<T>(', '_FakeFuture_0<T>('),
     );
   });
 
diff --git a/test/end2end/foo.dart b/test/end2end/foo.dart
index e57b925..6e8770a 100644
--- a/test/end2end/foo.dart
+++ b/test/end2end/foo.dart
@@ -20,6 +20,7 @@
   void returnsVoid() {}
   Future<void> returnsFutureVoid() => Future.value();
   Future<void>? returnsNullableFutureVoid() => Future.value();
+  Future<T> returnsFuture(T x) => Future.value(x);
   Bar returnsBar(int arg) => Bar();
 }
 
diff --git a/test/end2end/generated_mocks_test.dart b/test/end2end/generated_mocks_test.dart
index 62c9c7a..623ddcc 100644
--- a/test/end2end/generated_mocks_test.dart
+++ b/test/end2end/generated_mocks_test.dart
@@ -184,6 +184,11 @@
             .having((e) => e.toString(), 'toString()', contains('getter'))),
       );
     });
+
+    test('a method returning Future<T> can be stubbed', () async {
+      when(foo.returnsFuture(any)).thenAnswer((_) async => 1);
+      expect(await foo.returnsFuture(0), 1);
+    });
   });
 
   group('for a generated mock using unsupportedMembers', () {
@@ -278,7 +283,7 @@
   });
 
   group('for a generated nice mock', () {
-    late Foo<Object> foo;
+    late Foo<Bar> foo;
 
     setUp(() {
       foo = MockFooNice();
@@ -315,6 +320,28 @@
           throwsA(isA<FakeUsedError>().having(
               (e) => e.toString(), 'toString()', contains('returnsBar(43)'))));
     });
+    group('a method returning Future<T>', () {
+      final bar = Bar();
+      tearDown(() {
+        resetMockitoState();
+      });
+      test('returns a fake future if unstubbed', () {
+        expect(foo.returnsFuture(bar), isA<SmartFake>());
+      });
+      test('returned fake future cannot be awaited', () async {
+        try {
+          await foo.returnsFuture(bar);
+          // Expect it to throw.
+          expect('This code should not be reached', false);
+        } catch (e) {
+          expect(e, isA<FakeUsedError>());
+        }
+      });
+      test('with provideDummy returned value can be awaited', () async {
+        provideDummy<Bar>(bar);
+        expect(await foo.returnsFuture(MockBar()), bar);
+      });
+    });
   });
 
   test('a generated mock can be used as a stub argument', () {