Test Widget in Flutter

Hi everybody!
In this post we are going to see how test our widgets.
We suppose we have a User with images and we need show them in a collection. In this case, we have a GridView with Images like children. Something like this:
import 'package:flutter/material.dart';class UserImagesView extends StatelessWidget {
final List<String> userImages;UserImagesView(this.userImages);@override
Widget build(BuildContext context) {
if (userImages != null && userImages.isNotEmpty) {
return _buildPosts(userImages);
}
return Container();
}Widget _buildPosts(List<String> userImages) {
return Container(
child: GridView.count(
// This key is important.
key: Key('UserImagesGridView'),
padding: const EdgeInsets.all(8.0),
mainAxisSpacing: 2.0,
crossAxisSpacing: 2.0,
crossAxisCount: 3,
childAspectRatio: 1.0,
shrinkWrap: true,
scrollDirection: Axis.vertical,
children: userImages.map((userImage) {
return Image.network(
userImage,
fit: BoxFit.cover,
// This keys are important too.
key: Key('$userImage'),
);
}).toList(),
),
);
}
}
NOTE: userImages is a simple collection of URLs for simplify example.
As a good practice is very important test our code in whatever layer. So, we need add flutter test dependency if not present in pubspec.yaml file:
dev_dependencies:
flutter_test:
sdk: flutter
Our first use case will be if “there is no images then show empty container”, then we need create a test like the following code:
void main() {testWidgets("There is no images then show empty container", (WidgetTester tester) async {
// Empty urls
var images = List<String>();// Pump our widget to test. In this case is UserImagesView
await tester.pumpWidget(UserImagesView(images));// Get Find reference and get a Container type for assert it.
var container = find.byType(Container);// We expect there is only one Container in view.
expect(container, findsOneWidget);// Get Find reference again and get a GridView type for assert it.
var gridView = find.byType(GridView);// Also, we expected there is not a GridView in view.
expect(gridView, findsNothing);
});}
Run code and green, good for us!. We have test in our code and if we make some change in view, the test will tell me if something went wrong.
In that first test, we test only return Container();. Now, we are going to test the following piece of code:
if (userImages != null && userImages.isNotEmpty) {
return _buildPosts(userImages);
}
For that, we can write a new test:
testWidgets("There is one image then show gridView", (WidgetTester tester) async {// Create a list with one URL.
var images = List<String>()..add("some-url");// Pump our UserImagesView
await tester.pumpWidget(UserImagesView(images));// We get Key from GridView
final key = find.byKey(Key('UserImagesGridView'));// And we expected GridView was shown.
expect(key, findsOneWidget);
});
Run test and ….. red!. Something went wrong!. But what could be wrong? … Error message was: ’package:flutter/src/painting/basic_types.dart’: Failed assertion: line 222 pos 10: ‘textDirection != null’: is not true.
We didn’t some text or something like that. What is happen here?
After some attempts and few hours, I find the solution. Flutter has a widget called MaterialApp which initialize some widget for you, so we need wrap our UserImagesView in a MaterialApp only for test.
Widget makeTesteableWidget({Widget child}) {
return MaterialApp(
home: child,
);
}
Then, we test looks like:
testWidgets("There is one image then show gridView", (WidgetTester tester) async {
var images = List<String>()..add("some-url");final widget = makeTesteableWidget(
child: UserImagesView(images),
);await tester.pumpWidget(widget);final key = find.byKey(Key('UserImagesGridView'));
expect(key, findsOneWidget);
});
Run test and ….. red again!. Oh god! What is happen here!? Why do you hate my test!?. Don’t worry, in this time the error message is very clear: Exception: HTTP request failed, statusCode: 400.
We are using Image.network and in fact, we are wrong in this case. We must not depend on external services in our test, this is a bad practice.
In order to make green our test, we need mock all call to internet. For that, add the following dependency:
image_test_utils: ^1.0.0
Now, we can use provideMockedNetworkImages method which mock HttpClient for us and the code will be:
testWidgets("There is one image then show gridView", (WidgetTester tester) async {
provideMockedNetworkImages(() async {
var images = List<String>()..add("some-url");final widget = makeTesteableWidget(
child: UserImagesView(images),
);await tester.pumpWidget(widget);final key = find.byKey(Key('UserImagesGridView'));
expect(key, findsOneWidget);
});
});
Finally, all green and our widget are tested!.
If you want, you can read my previous article user location in flutter.
That’s all folks!
Enjoy!