On this tutorial about Widget Testing with Flutter, you’ll discover ways to guarantee UI widgets look and behave as anticipated by writing check code.
Replace observe: Stephanie Patterson up to date this tutorial for Flutter 3.3 and Dart 2.18. Lawrence Tan wrote the unique.
Testing is vital throughout your app growth. As your product grows, it will get extra complicated, and performing guide exams turns into tougher. Having an automatic testing setting helps optimize this course of.
Widget testing is like UI testing: You develop the feel and appear of your app, making certain each interplay the consumer makes produces the anticipated end result.
For this tutorial, you’ll write widget exams for a automobile app known as Drive Me, which lets customers view a listing of automobiles, view particulars about them and choose and examine particular automobiles. Within the course of, you’ll discover ways to check that the app correctly performs the next features:
- Masses mock knowledge to the widget exams.
- Injects faulty mock knowledge for unfavourable exams.
- Ensures that the app presents a listing of sorted automobiles and shows its particulars.
- Checks that the automobile choice seems appropriately within the listing web page.
- Ensures that the automobile particulars web page shows appropriately.
Getting Began
To begin, obtain the starter undertaking by clicking the Obtain Supplies button on the prime or backside of the tutorial, then discover the starter undertaking in Visible Studio Code. You may also use Android Studio, however this tutorial makes use of Visible Studio Code in its examples.
Make certain to run flutter packages get
both on the command line or when prompted by your IDE. This pulls the most recent model of the packages wanted for this undertaking.
Notice: Within the starter undertaking, you’ll probably see warnings about Unused import
or variables not getting used. Ignore these as they are going to be used by the point you’ve gotten accomplished this tutorial.
Construct and run the undertaking with flutter run
to familiarize your self with how the app works.
Exploring the Starter Challenge
The starter undertaking consists of the implementation of the app so you’ll be able to concentrate on widget testing. Check out the contents in lib to know how the app works.
Beginning on the backside, as you realize fundamental.dart is the file the place all Flutter apps begin. dependency_injector.dart is the place the app registers the primary knowledge layer courses and injects them through get_it
.
constants.dart accommodates many of the variables you’ll use all through the app.
The undertaking has 4 fundamental folders:
- database
- particulars
- listing
- fashions
Within the lib/fashions folder, you’ll discover an vital file. automobile.dart is the place the Automotive()
and CarsList()
mannequin implementations reside. The CarsList()
mannequin holds a listing of automobiles and an error message if an exception happens.
Subsequent, have a look at lib/listing/cars_list_bloc.dart. That is the CarsList()
knowledge layer. CarsListBloc
hundreds knowledge from the JSON present in belongings/sample_data/knowledge.json and passes it to the widget listing. Thereafter, it kinds the automobiles alphabetically through alphabetizeItemsByTitleIgnoreCases()
.
Within the lib/particulars folder is car_details_bloc.dart, which will get knowledge from CarsListBloc
and passes it to the CarDetails
widget in car_details_page.dart.
Open lib/particulars/car_details_page.dart. You’ll see that on init
it retrieves the info handed in by CarDetailsBloc
and presents it on the widget. When customers choose or deselect gadgets, CarsListBloc()
makes the updates.
When the consumer selects any automobile, a separate knowledge stream manages it.
lib/database accommodates cars_database.dart, which implements an summary class known as CarsDataProvider
. This class accommodates loadCars()
that parses the JSON file containing a listing of automobile knowledge. The parsed knowledge returned is a CarsList()
.
As you guessed it from some filenames, this undertaking makes use of BLoC
to go knowledge between the widgets layer and the info layer.
Now that you simply’ve tried the app and perceive the implementation particulars, it’s time to begin operating some exams.
Earlier than you dive deep into the subject of widget testing with Flutter, take a step again and evaluate it with unit testing.
Unit Testing vs. Widget Testing
Unit testing is a course of the place you test for high quality, efficiency or reliability by writing further code that ensures your app logic works as anticipated. It exams for logic written in features and strategies. The unit exams then develop and accumulate to cowl a complete class and subsequently an enormous a part of the undertaking, if not all.
The purpose of a widget check is to confirm that each widget’s UI appears to be like and behaves as anticipated. Essentially, you carry out exams by re-rendering the widgets in code with mock knowledge.
This additionally tells you that in the event you modify the logic of the app — for instance, you alter the login validation of the username from a minimal of six characters to seven — then your unit check and widget check might each fail collectively.
Checks lock down your app’s options, which enable you to to correctly plan your app’s design earlier than creating it.
Testing Pyramid
There are three varieties of exams you’ll be able to carry out with Flutter:
- Unit exams: Used to check a technique or class.
- Widget exams: These check a single widget.
- Integration exams: Use these to check the essential flows of all the app.
So, what number of exams will you want? To determine, check out the testing pyramid. It summarizes the important varieties of exams a Flutter app ought to have:
Basically, unit exams ought to cowl many of the app, then widget exams and, lastly, integration exams.
Even when good testing grounds are in place, you shouldn’t omit guide testing.
As you go up the pyramid, the exams get much less remoted and extra built-in. Writing good unit exams enable you to construct a robust base on your app.
Now that you simply perceive the necessity for testing, it’s time to dive into the undertaking for this tutorial!
Widget Testing the Automotive Checklist
Open check/listing/cars_list_bloc_test.dart. Look beneath // TODO 3: Unit Testing Knowledge Loading Logic
and also you’ll see the unit exams applied on this undertaking. These unit exams be certain that the info construction you present to the widget is correct.
Earlier than going into writing the check scripts, it’s good to have a look at the precise display you’re testing. In check/database/mock_car_data_provider.dart, the consumer has chosen the primary automobile — the Hyundai Sonata 2017, proven the picture beneath:
Are you prepared to begin including widget exams?
Your First Take a look at
Open check/listing/cars_list_page_test.dart and add the next beneath // TODO 4: Inject and Load Mock Automotive Knowledge
:
carsListBloc.injectDataProviderForTest(MockCarDataProvider());
That is injecting the mock automobile check knowledge into carsListBloc
.
Beneath // TODO 5: Load & Kind Mock Knowledge for Verification
add:
last automobiles = await MockCarDataProvider().loadCars();
automobiles.gadgets.type(carsListBloc.alphabetizeItemsByTitleIgnoreCases);
Right here you’re ready for the mock automobile knowledge to load after which type the listing.
Injecting check knowledge
Now it’s time to inject the check knowledge.
Add these traces of code beneath // TODO 6: Load and render Widget
:
await tester.pumpWidget(const ListPageWrapper());
await tester.pump(Period.zero);
pumpWidget()
renders and performs a runApp
of a stateless ListPage
widget wrapped in ListPageWrapper()
. Then, you name pump()
to render the body and specify how lengthy to attend. On this case you don’t desire a delay so Period.zero
is used.
This prepares the widget for testing!
pumpWidget
calls runApp
, and in addition triggers a body to color the app. That is enough in case your UI and knowledge are all supplied instantly from the app, or you possibly can name them static knowledge (i.e., labels and texts).
When you’ve gotten a construction (i.e. listing, collections) with repeated knowledge fashions, pump()
turns into important to set off a rebuild for the reason that data-loading course of will occur post-runApp
.
Guaranteeing visibility
Beneath // TODO 7: Examine Vehicles Checklist's part's existence through key
to make sure that the Carslist
is within the view add these traces of code:
last carListKey = discover.byKey(const Key(carsListKey));
count on(carListKey, findsOneWidget);
Should you have a look at lib/listing/cars_list_page.dart, you will note that the widget tree identifies ListView()
with a key known as carsListKey()
. findsOneWidget
makes use of a matcher to find precisely one such widget.
The mock knowledge in mock_car_data_provider.dart has a complete of six automobiles, however you don’t need to write a check for each. A great apply is to make use of a for
loop to iterate by way of and confirm every automobile on the listing.
Return to check/listing/cars_list_page_test.dart and beneath // TODO 8: Create a operate to confirm listing's existence
add this:
void _verifyAllCarDetails(
Checklist<Automotive> carsList,
WidgetTester tester,
) async {
for (last automobile in carsList) {
last carTitleFinder = discover.textual content(automobile.title);
last carPricePerDayFinder = discover.textual content(
pricePerDayText.replaceFirst(
wildString,
automobile.pricePerDay.toStringAsFixed(2),
),
);
await tester.ensureVisible(carTitleFinder);
count on(carTitleFinder, findsOneWidget);
await tester.ensureVisible(carPricePerDayFinder);
count on(carPricePerDayFinder, findsOneWidget);
}
}
This check verifies that the title and the value per day show appropriately. That is potential due to a operate known as ensureVisible()
.
To see extra about ensureVisible()
, hover over it to see its description robotically displayed.
ListView
in a SingleChildScrollView
to make this work in cars_list_page.dart. On the time of writing, you will need to do that for the check to go.
Theoretically, a ListView
additionally accommodates a scrollable factor to permit scrolling. The check doesn’t at present confirm photos.
Testing photos is pricey: It requires getting knowledge from the community and verifying chunks of information. This will result in an extended check period because the variety of check instances will increase.
To confirm the automobile particulars, discover // TODO 9: Name Confirm Automotive Particulars operate
and add this beneath it to name to the operate you simply created:
_verifyAllCarDetails(automobiles.gadgets, tester);
Within the subsequent part you’ll discover ways to add exams to confirm the chosen automobile has a blue background.
Widget Testing the Automotive Checklist Web page with Choice
Bear in mind when you choose a automobile it has a blue background? It’s worthwhile to create a check to make sure that occurs.
Nonetheless in cars_list_page_test.dart, add this beneath // TODO 10: Choose a Automotive
:
carsListBloc.selectItem(1);
The widget tester makes an attempt to pick Automotive ID 1.
Discover // TODO 11: Confirm that Automotive is highlighted in blue
add the next beneath it:
// 1
bool widgetSelectedPredicate(Widget widget) =>
widget is Card && widget.colour == Colours.blue.shade200;
// 2
bool widgetUnselectedPredicate(Widget widget) =>
widget is Card && widget.colour == Colours.white;
count on(
discover.byWidgetPredicate(widgetSelectedPredicate),
findsOneWidget,
);
count on(
discover.byWidgetPredicate(widgetUnselectedPredicate),
findsNWidgets(5),
);
Right here you’ve gotten created two predicates:
- Confirm the chosen card has a blue background
- Make sure the unselected card stays white
Run this check now. Hurray, your new check passes! :]
You’re doing very nicely. It’s time to attempt some unfavourable exams earlier than ending with the testing of the automobile particulars web page.
Damaging Checks for Automotive Checklist Web page
Now it’s time to check for errors. To simulate errors, add the next beneath // TODO 12: Inject and Load Error Mock Automotive Knowledge
:
carsListBloc.injectDataProviderForTest(MockCarDataProviderError());
You’ve injected knowledge earlier than. The one distinction right here is that you simply inject MockCarDataProviderError()
, which accommodates mock error knowledge.
Beneath // TODO 13: Load and render Widget
add:
await tester.pumpWidget(const ListPageWrapper());
await tester.pump(Period.zero);
As earlier than, pumpWidget()
and pump()
set off a body to color and render instantly.
Beneath // TODO 14: Confirm that Error Message is proven
add the next so as to add error messages.
last errorFinder = discover.textual content(
errorMessage.replaceFirst(
errorMessage,
mockErrorMessage,
),
);
count on(errorFinder, findsOneWidget);
This replaces the errorMessage
with the mockErrorMessage
and confirms the error message shows.
Prepared on your fifth check? Run it.
Nice job! Your fifth check handed!
Verifying view replace
There’s one final check you might want to carry out for this widget, which is to confirm the widget updates its view if knowledge is available in after getting an error.
It’s worthwhile to check in case your app doesn’t have any automobiles to show.
Since this subsequent step consists of code you’ve already used, you’re going to do a big replace directly. Discover and substitute // TODO Exchange testWidgets('''After encountering an error...'''
and all the placeholder testWidgets()
beneath it with:
testWidgets(
'''After encountering an error, and stream is up to date, Widget can be
up to date.''',
(WidgetTester tester) async {
// TODO 15: Inject and Load Error Mock Automotive Knowledge
carsListBloc.injectDataProviderForTest(MockCarDataProviderError());
// TODO 16: Load and render Widget
await tester.pumpWidget(const ListPageWrapper());
await tester.pump(Period.zero);
// TODO 17: Confirm that Error Message and Retry Button is proven
last errorFinder = discover.textual content(
errorMessage.replaceFirst(
errorMessage,
mockErrorMessage,
),
);
last retryButtonFinder = discover.textual content(retryButton);
count on(errorFinder, findsOneWidget);
count on(retryButtonFinder, findsOneWidget);
// TODO 18: Inject and Load Mock Automotive Knowledge
carsListBloc.injectDataProviderForTest(MockCarDataProvider());
await tester.faucet(retryButtonFinder);
// TODO 19: Reload Widget
await tester.pump(Period.zero);
// TODO 20: Load and Confirm Automotive Knowledge
last automobiles = await MockCarDataProvider().loadCars();
_verifyAllCarDetails(automobiles.gadgets, tester);
},
);
Right here’s what the code does:
- TODO 15–17: These are the identical because the exams you probably did within the final step.
- TODO 18: Injects automobile mock knowledge.
- TODO 19: Reloads the widget.
- TODO 20: Waits for mock dat to load after which verifies the automobile particulars.
Time to run the check. Run it now, and …
Superior work! Your sixth check passes!
You’ve examined for when a automobile is chosen. What about when it’s been deselected? You guessed it, that’s subsequent.
Widget Testing the Automotive Particulars Web page for the Deselected Automotive
Take one other have a look at the Automotive Particulars Web page. Right here is an instance of a particular automobile and one other that has not been chosen.
Discover how the title and button textual content are totally different relying on the consumer’s alternative. It’s worthwhile to check for that.
Open check/particulars/car_details_page_test.dart
add substitute // TODO Exchange testWidgets('Unselected Automotive Particulars Web page...'
together with the corresponding placeholder testWidgets()
code with this:
testWidgets(
'Unselected Automotive Particulars Web page must be proven as Unselected',
(WidgetTester tester) async {
// TODO 21: Inject and Load Mock Automotive Knowledge
carsListBloc.injectDataProviderForTest(MockCarDataProvider());
await carsListBloc.loadItems();
// TODO 22: Load & Kind Mock Knowledge for Verification
last automobiles = await MockCarDataProvider().loadCars();
automobiles.gadgets.type(carsListBloc.alphabetizeItemsByTitleIgnoreCases);
// TODO 23: Load and render Widget
await tester.pumpWidget(
const DetailsPageSelectedWrapper(2)); // Mercedes-Benz 2017
await tester.pump(Period.zero);
// TODO 24: Confirm Automotive Particulars
last carDetailKey = discover.byKey(const Key(carDetailsKey));
count on(carDetailKey, findsOneWidget);
last pageTitleFinder =
discover.textual content(automobiles.gadgets[1].title); // 2nd automobile in sorted listing
count on(pageTitleFinder, findsOneWidget);
last notSelectedTextFinder = discover.textual content(notSelectedTitle);
count on(notSelectedTextFinder, findsOneWidget);
last descriptionTextFinder = discover.textual content(automobiles.gadgets[1].description);
count on(descriptionTextFinder, findsOneWidget);
last featuresTitleTextFinder = discover.textual content(featuresTitle);
count on(featuresTitleTextFinder, findsOneWidget);
last allFeatures = StringBuffer();
for (last function in automobiles.gadgets[1].options) {
allFeatures.write('n $function n');
}
last featureTextFinder = discover.textual content(allFeatures.toString());
await tester.ensureVisible(featureTextFinder);
count on(featureTextFinder, findsOneWidget);
last selectButtonFinder = discover.textual content(selectButton);
await tester.scrollUntilVisible(selectButtonFinder, 500.0);
count on(selectButtonFinder, findsOneWidget);
},
);
Right here’s what you completed with the code above:
- TODO 21–23: As soon as once more, you inject, load and type the info, then put together and pump the widget.
-
TODO 24: Should you open lib/particulars/car_details_page.dart, you’ll discover a widget that’s recognized with a key, a web page title, a deselected title, a options listing and a
selectButton
. The code on thisTODO
lets you confirm these widgets!scrollUntilVisible()
scrolls by way of the scrollable widget, in your app’s case theListView
widget, till the anticipated widget is discovered.
Time to run your exams.
You could have created a wide range of exams. Nice job! Are you prepared for a problem?
Widget Testing Problem
Your problem is to make use of what you’ve study and full the ultimate exams by yourself. You are able to do it!
Should you get caught or need to evaluate options, simply click on the Reveal button. Give it a attempt first. :]
- The chosen Automotive Particulars Web page ought to present a static Chosen textual content on the prime of the web page. When viewing a particular automobile, the main points web page must be represented appropriately.
- When deciding on and deselecting a automobile, the main points web page ought to replace accordingly.
The answer is damaged up into two exams. You’ll nonetheless be working in car_details_page_test.dart. Trace, TODO 25–28 and TODO 29–32 are listed within the undertaking.
[spoiler]
Testing Particulars Web page for Chosen Vehicles
TODO 25–28:
testWidgets(
'Chosen Automotive Particulars Web page must be proven as Chosen',
(WidgetTester tester) async {
// TODO 25: Inject and Load Mock Automotive Knowledge
carsListBloc.injectDataProviderForTest(MockCarDataProvider());
await carsListBloc.loadItems();
// TODO 26: Load and render Widget
await tester.pumpWidget(
const DetailsPageSelectedWrapper(3)); // Hyundai Sonata 2017
await tester.pump(Period.zero);
// TODO 27: Load Mock Knowledge for Verification
last actualCarsList = await MockCarDataProvider().loadCars();
last actualCars = actualCarsList.gadgets;
// TODO 28: First Automotive is Chosen, so Confirm that
last carDetailKey = discover.byKey(const Key(carDetailsKey));
count on(carDetailKey, findsOneWidget);
last pageTitleFinder = discover.textual content(actualCars[2].title);
count on(pageTitleFinder, findsOneWidget);
last notSelectedTextFinder = discover.textual content(selectedTitle);
count on(notSelectedTextFinder, findsOneWidget);
last descriptionTextFinder = discover.textual content(actualCars[2].description);
count on(descriptionTextFinder, findsOneWidget);
last featuresTitleTextFinder = discover.textual content(featuresTitle);
count on(featuresTitleTextFinder, findsOneWidget);
last actualFeaturesStringBuffer = StringBuffer();
for (last function in actualCars[2].options) {
actualFeaturesStringBuffer.write('n $function n');
}
last featuresTextFinder =
discover.textual content(actualFeaturesStringBuffer.toString());
await tester.ensureVisible(featuresTextFinder);
count on(featuresTextFinder, findsOneWidget);
last selectButtonFinder = discover.textual content(removeButton);
//await tester.ensureVisible(selectButtonFinder);
await tester.scrollUntilVisible(selectButtonFinder, 500.0);
count on(selectButtonFinder, findsOneWidget);
},
);
Take a look at that the Chosen Automotive Updates the Widget
TODO 29–32:
testWidgets(
'Choosing Automotive Updates the Widget',
(WidgetTester tester) async {
// TODO 29: Inject and Load Mock Automotive Knowledge
carsListBloc.injectDataProviderForTest(MockCarDataProvider());
await carsListBloc.loadItems();
// TODO 30: Load & Kind Mock Knowledge for Verification
last automobiles = await MockCarDataProvider().loadCars();
automobiles.gadgets.type(carsListBloc.alphabetizeItemsByTitleIgnoreCases);
// TODO 31: Load and render Widget for the primary automobile
await tester.pumpWidget(
const DetailsPageSelectedWrapper(2)); // Mercedes-Benz 2017
await tester.pump(Period.zero);
// TODO 32: Faucet on Choose and Deselect to make sure widget updates
last selectButtonFinder = discover.textual content(selectButton);
await tester.scrollUntilVisible(selectButtonFinder, 500.0);
await tester.faucet(selectButtonFinder);
await tester.pump(Period.zero);
last deselectButtonFinder = discover.textual content(removeButton);
//await tester.ensureVisible(deselectButtonFinder);
await tester.scrollUntilVisible(deselectButtonFinder, 500.0);
await tester.faucet(deselectButtonFinder);
await tester.pump(Period.zero);
last newSelectButtonFinder = discover.textual content(selectButton);
//await tester.ensureVisible(newSelectButtonFinder);
await tester.scrollUntilVisible(newSelectButtonFinder, 500.0);
count on(newSelectButtonFinder, findsOneWidget);
},
);
[/spoiler]
After you’ve completed your problem, rerun your exams. There are 9 exams they usually’ve all handed! :]
Congratulations! You’re now an official Widget Testing Ambassador, go forth and unfold the excellent news!
The place to Go From Right here?
Obtain the ultimate undertaking by clicking the Obtain Supplies button on the prime or backside of this tutorial.
In your subsequent steps, increase your Flutter testing information by exploring the official UI exams cookbook from the Flutter workforce.
Then take your testing to the subsequent degree by exploring and integrating Mockito to mock dwell net companies and databases.
We hope you loved this tutorial. When you have any questions or feedback, please be a part of the discussion board dialogue beneath!