Widget Testing With Flutter: Getting Started
In this tutorial about Widget Testing with Flutter, you’ll learn how to ensure UI widgets look and behave as expected by writing test code. By Stef Patterson.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Widget Testing With Flutter: Getting Started
20 mins
- Getting Started
- Exploring the Starter Project
- Unit Testing vs. Widget Testing
- Testing Pyramid
- Widget Testing the Car List
- Your First Test
- Injecting test data
- Ensuring visibility
- Widget Testing the Car List Page with Selection
- Negative Tests for Car List Page
- Verifying view update
- Widget Testing the Car Details Page for the Deselected Car
- Widget Testing Challenge
- Testing Details Page for Selected Cars
- Test that the Selected Car Updates the Widget
- Where to Go From Here?
Widget Testing the Car Details Page for the Deselected Car
Take another look at the Car Details Page. Here is an example of a selected car and another that has not been selected.
Notice how the title and button text are different depending on the user’s choice. You need to test for that.
Open test/details/car_details_page_test.dart
add replace // TODO Replace testWidgets('Unselected Car Details Page...'
along with the corresponding placeholder testWidgets()
code with this:
testWidgets(
'Unselected Car Details Page should be shown as Unselected',
(WidgetTester tester) async {
// TODO 21: Inject and Load Mock Car Data
carsListBloc.injectDataProviderForTest(MockCarDataProvider());
await carsListBloc.loadItems();
// TODO 22: Load & Sort Mock Data for Verification
final cars = await MockCarDataProvider().loadCars();
cars.items.sort(carsListBloc.alphabetizeItemsByTitleIgnoreCases);
// TODO 23: Load and render Widget
await tester.pumpWidget(
const DetailsPageSelectedWrapper(2)); // Mercedes-Benz 2017
await tester.pump(Duration.zero);
// TODO 24: Verify Car Details
final carDetailKey = find.byKey(const Key(carDetailsKey));
expect(carDetailKey, findsOneWidget);
final pageTitleFinder =
find.text(cars.items[1].title); // 2nd car in sorted list
expect(pageTitleFinder, findsOneWidget);
final notSelectedTextFinder = find.text(notSelectedTitle);
expect(notSelectedTextFinder, findsOneWidget);
final descriptionTextFinder = find.text(cars.items[1].description);
expect(descriptionTextFinder, findsOneWidget);
final featuresTitleTextFinder = find.text(featuresTitle);
expect(featuresTitleTextFinder, findsOneWidget);
final allFeatures = StringBuffer();
for (final feature in cars.items[1].features) {
allFeatures.write('\n $feature \n');
}
final featureTextFinder = find.text(allFeatures.toString());
await tester.ensureVisible(featureTextFinder);
expect(featureTextFinder, findsOneWidget);
final selectButtonFinder = find.text(selectButton);
await tester.scrollUntilVisible(selectButtonFinder, 500.0);
expect(selectButtonFinder, findsOneWidget);
},
);
Here’s what you accomplished with the code above:
- TODO 21–23: Once again, you inject, load and sort the data, then prepare and pump the widget.
-
TODO 24: If you open lib/details/car_details_page.dart, you’ll find a widget that’s identified with a key, a page title, a deselected title, a features list and a
selectButton
. The code in thisTODO
helps you to verify these widgets!scrollUntilVisible()
scrolls through the scrollable widget, in your app’s case theListView
widget, until the expected widget is found.
Time to run your tests.
You have created a variety of tests. Great job! Are you ready for a challenge?
Your challenge is to use what you’ve learn and complete the final tests on your own. You can do it!
If you get stuck or want to compare solutions, just click the Reveal button. Give it a try first. :]
The solution is broken up into two tests. You’ll still be working in car_details_page_test.dart. Hint, TODO 25–28 and TODO 29–32 are listed in the project.
- The selected Car Details Page should show a static Selected text at the top of the page. When viewing a selected car, the details page should be represented correctly.
- When selecting and deselecting a car, the details page should update accordingly.
The solution is broken up into two tests. You’ll still be working in car_details_page_test.dart. Hint, TODO 25–28 and TODO 29–32 are listed in the project.
- The selected Car Details Page should show a static Selected text at the top of the page. When viewing a selected car, the details page should be represented correctly.
- When selecting and deselecting a car, the details page should update accordingly.
The solution is broken up into two tests. You’ll still be working in car_details_page_test.dart. Hint, TODO 25–28 and TODO 29–32 are listed in the project.
[spoiler]
TODO 25–28:
testWidgets(
'Selected Car Details Page should be shown as Selected',
(WidgetTester tester) async {
// TODO 25: Inject and Load Mock Car Data
carsListBloc.injectDataProviderForTest(MockCarDataProvider());
await carsListBloc.loadItems();
// TODO 26: Load and render Widget
await tester.pumpWidget(
const DetailsPageSelectedWrapper(3)); // Hyundai Sonata 2017
await tester.pump(Duration.zero);
// TODO 27: Load Mock Data for Verification
final actualCarsList = await MockCarDataProvider().loadCars();
final actualCars = actualCarsList.items;
// TODO 28: First Car is Selected, so Verify that
final carDetailKey = find.byKey(const Key(carDetailsKey));
expect(carDetailKey, findsOneWidget);
final pageTitleFinder = find.text(actualCars[2].title);
expect(pageTitleFinder, findsOneWidget);
final notSelectedTextFinder = find.text(selectedTitle);
expect(notSelectedTextFinder, findsOneWidget);
final descriptionTextFinder = find.text(actualCars[2].description);
expect(descriptionTextFinder, findsOneWidget);
final featuresTitleTextFinder = find.text(featuresTitle);
expect(featuresTitleTextFinder, findsOneWidget);
final actualFeaturesStringBuffer = StringBuffer();
for (final feature in actualCars[2].features) {
actualFeaturesStringBuffer.write('\n $feature \n');
}
final featuresTextFinder =
find.text(actualFeaturesStringBuffer.toString());
await tester.ensureVisible(featuresTextFinder);
expect(featuresTextFinder, findsOneWidget);
final selectButtonFinder = find.text(removeButton);
//await tester.ensureVisible(selectButtonFinder);
await tester.scrollUntilVisible(selectButtonFinder, 500.0);
expect(selectButtonFinder, findsOneWidget);
},
);
TODO 29–32:
testWidgets(
'Selecting Car Updates the Widget',
(WidgetTester tester) async {
// TODO 29: Inject and Load Mock Car Data
carsListBloc.injectDataProviderForTest(MockCarDataProvider());
await carsListBloc.loadItems();
// TODO 30: Load & Sort Mock Data for Verification
final cars = await MockCarDataProvider().loadCars();
cars.items.sort(carsListBloc.alphabetizeItemsByTitleIgnoreCases);
// TODO 31: Load and render Widget for the first car
await tester.pumpWidget(
const DetailsPageSelectedWrapper(2)); // Mercedes-Benz 2017
await tester.pump(Duration.zero);
// TODO 32: Tap on Select and Deselect to ensure widget updates
final selectButtonFinder = find.text(selectButton);
await tester.scrollUntilVisible(selectButtonFinder, 500.0);
await tester.tap(selectButtonFinder);
await tester.pump(Duration.zero);
final deselectButtonFinder = find.text(removeButton);
//await tester.ensureVisible(deselectButtonFinder);
await tester.scrollUntilVisible(deselectButtonFinder, 500.0);
await tester.tap(deselectButtonFinder);
await tester.pump(Duration.zero);
final newSelectButtonFinder = find.text(selectButton);
//await tester.ensureVisible(newSelectButtonFinder);
await tester.scrollUntilVisible(newSelectButtonFinder, 500.0);
expect(newSelectButtonFinder, findsOneWidget);
},
);
[/spoiler]
After you’ve finished your challenge, rerun your tests. There are nine tests and they’ve all passed! :]
Congratulations! You’re now an official Widget Testing Ambassador, go forth and spread the good news!
Where to Go From Here?
Download the final project by clicking the Download Materials button at the top or bottom of this tutorial.
For your next steps, expand your Flutter testing knowledge by exploring the official UI tests cookbook from the Flutter team.
Then take your testing to the next level by exploring and integrating Mockito to mock live web services and databases.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!