Q:

Testing GestureDetector on Image widget

I have made a simple test case app where you click a widget using GestureDetector which triggers an update using setState to the tapCount variable.
The app is working in the emulator with the text updating correctly as shown above, but as soon as I try a Flutter widget test, the widget test fails as the text does not update correctly in the test environment.
Reproducible example:
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  MyApp();

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  int tapCount = 0;

  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SafeArea(
          child: Column(
            children: <Widget>[
              MyImage(
                onTap: () {
                  setState(() {
                    tapCount += 1;
                  });
                },
                imagePath: 'assets/my-image.jpg',
              ),
              Text(tapCount.toString())
            ],
          ),
        ),
      ),
    );
  }
}

class MyImage extends StatelessWidget {
  final Function() onTap;
  final String imagePath;

  const MyImage({
    Key key,
    @required this.onTap,
    @required this.imagePath,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        this.onTap();
      },
      child: Image.asset(
        imagePath,
        height: 100.0,
      ),
    );
  }
}

In the pubspec, I downloaded a random image and verified the image successfully displays in the emulator.
  assets:
    - assets/my-image.jpg

My test is the same as the sample with the addition of await tester.pumpAndSettle(); and tapping the image:
void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(MyApp());
    await tester.pumpAndSettle();

    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // Tap the image and trigger a frame.
    await tester.tap(find.byType(MyImage));
    await tester.pump();
    await tester.pumpAndSettle();

    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing); // this test fails
    expect(find.text('1'), findsOneWidget); // this test fails
  });
}

When I run the test I get this error
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure object was thrown running a test:
  Expected: no matching nodes in the widget tree
  Actual: ?:<exactly one widget with text "0" (ignoring offstage widgets): Text("0")>
   Which: means one was found but none were expected

When the exception was thrown, this was the stack:
#4      main.<anonymous closure> (file:///Projects/untitled/test/widget_test.dart:27:5)
<asynchronous suspension>
#5      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:82:23)
#6      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:566:19)
<asynchronous suspension>
#9      TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:550:14)
#10     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:893:24)
#16     AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:890:15)
#17     testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:81:22)
#18     Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:168:27)
<asynchronous suspension>
#19     Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:249:15)
<asynchronous suspension>
#24     Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:246:5)
#25     Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:166:33)
#30     Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:165:13)
<asynchronous suspension>
#31     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:399:25)
<asynchronous suspension>
#45     _Timer._runTimers (dart:isolate/runtime/libtimer_impl.dart:382:19)
#46     _Timer._handleMessage (dart:isolate/runtime/libtimer_impl.dart:416:5)
#47     _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:171:12)
(elided 28 frames from class _FakeAsync, package dart:async, and package stack_trace)

This was caught by the test expectation on the following line:
  file:///Projects/untitled/test/widget_test.dart line 27

The test description was:
Counter increments smoke test
════════════════════════════════════════════════════════════════════════════════════════════════════

Test failed. See exception logs above.
The test description was: Counter increments smoke test

If I try the same test, but with the Image inside MyImage replaced with another widget (e.g. another Text widget) inside main.dart, the test passes:
class MyImage extends StatelessWidget {
  final Function() onTap;
  final String imagePath;

  const MyImage({
    Key key,
    @required this.onTap,
    @required this.imagePath,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        this.onTap();
      },
      child: Text( // replaced Image with Text and test passes!
        imagePath,
      ),
    );
  }
}

This makes me think the issue is due to using the Image, but I can't figure out why.
Code is also uploaded on GitHub if you want to try the test.

A:

Here is my take on why it's not working with an image. The flutter tests run in a FakeAsync zone and when you need to run real async code like loading an asset through an assetBundle the asset is not getting loaded and the image widget's size stays as zero and because of this the hit testing fails. If you set height and width of the image before hand the test passes.

