Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing GestureDetector on Image widget

Tags:

flutter

dart

app with issue

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.

like image 568
S.D. Avatar asked Jan 20 '19 03:01

S.D.


People also ask

What is widget testing?

The widget test is testing UI components, so it is also called Component Testing. It is used to test a single widget. The main goal of widget testing is to check whether the widget works as expected. A user can write test cases for every widget present in the project.

What is GestureDetector widget?

A widget that detects gestures. Attempts to recognize gestures that correspond to its non-null callbacks. If this widget has a child, it defers to that child for its sizing behavior.

What is the difference between InkWell and GestureDetector?

They both look the same and almost do the same thing, so what is difference between flutter InkWell vs GestureDetector? Generally speaking, GestureDetector provides more gesture control to detect almost every user interaction including dragging, swiping, pinching, etc.

How do you use GestureDetector?

Detect gestures. Android provides the GestureDetector class for detecting common gestures. Some of the gestures it supports include onDown() , onLongPress() , onFling() , and so on. You can use GestureDetector in conjunction with the onTouchEvent() method described above.


1 Answers

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.

like image 93
Mertus Avatar answered Oct 08 '22 18:10

Mertus