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.
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With