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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 3 of this article. Click here to view the first page.

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.

Selected and unselected cars

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 this TODO helps you to verify these widgets! scrollUntilVisible() scrolls through the scrollable widget, in your app’s case the ListView widget, until the expected widget is found.

Time to run your tests.

Unselected cars should show unselecte

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.

  1. 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.
  2. When selecting and deselecting a car, the details page should update accordingly.
Objectives:

[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! :]

9 test passed

Congratulations! You’re now an official Widget Testing Ambassador, go forth and spread the good news!

Flutter Widget Testing award

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!