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
Update note: Stephanie Patterson updated this tutorial for Flutter 3.3 and Dart 2.18. Lawrence Tan wrote the original.

Testing is important during your app development. As your product grows, it gets more complex, and performing manual tests becomes more difficult. Having an automated testing environment helps optimize this process.

Widget testing is like UI testing: You develop the look and feel of your app, ensuring every interaction the user makes produces the expected result.

For this tutorial, you’ll write widget tests for a car app called Drive Me, which lets users view a list of cars, view details about them and select and view specific cars. In the process, you’ll learn how to test that the app properly performs the following functions:

  • Loads mock data to the widget tests.
  • Injects erroneous mock data for negative tests.
  • Ensures that the app presents a list of sorted cars and displays its details.
  • Checks that the car selection appears correctly in the list page.
  • Ensures that the car details page displays correctly.
Note: This tutorial assumes you have some basic knowledge of Flutter. If you are new to this family, take a look at our Getting Started With Flutter tutorial before proceeding.

Getting Started

To start, download the starter project by clicking the Download Materials button at the top or bottom of the tutorial, then explore the starter project in Visual Studio Code. You can also use Android Studio, but this tutorial uses Visual Studio Code in its examples.

Make sure to run flutter packages get either at the command line or when prompted by your IDE. This pulls the latest version of the packages needed for this project.

Note: In the starter project, you will likely see warnings about Unused import or variables not being used. Ignore these as they will be used by the time you have completed this tutorial.

Build and run the project with flutter run to familiarize yourself with how the app works.

Car list

Exploring the Starter Project

The starter project includes the implementation of the app so you can focus on widget testing. Take a look at the contents in lib to understand how the app works.

Project Structure

Starting at the bottom, as you know main.dart is the file where all Flutter apps start. dependency_injector.dart is where the app registers the main data layer classes and injects them via get_it.

Note: If you want to learn more about get_it and how dependency injection works, read Unit Testing With Flutter.

constants.dart contains most of the variables you’ll use throughout the app.

The project has four main folders:

  • database
  • details
  • list
  • models

In the lib/models folder, you’ll find an important file. car.dart is where the Car() and CarsList() model implementations reside. The CarsList() model holds a list of cars and an error message if an exception occurs.

Next, look at lib/list/cars_list_bloc.dart. This is the CarsList() data layer. CarsListBloc loads data from the JSON found in assets/sample_data/data.json and passes it to the widget list. Thereafter, it sorts the cars alphabetically via alphabetizeItemsByTitleIgnoreCases().

In the lib/details folder is car_details_bloc.dart, which gets data from CarsListBloc and passes it to the CarDetails widget in car_details_page.dart.

Open lib/details/car_details_page.dart. You’ll see that on init it retrieves the data passed in by CarDetailsBloc and presents it on the widget. When users select or deselect items, CarsListBloc() makes the updates.

When the user selects any car, a separate data stream manages it.

lib/database contains cars_database.dart, which implements an abstract class called CarsDataProvider. This class contains loadCars() that parses the JSON file containing a list of car data. The parsed data returned is a CarsList().

As you guessed it from some filenames, this project uses BLoC to pass data between the widgets layer and the data layer.

Note: To learn more about BLoC, visit Getting Started With BLoC Pattern.

Now that you’ve tried the app and understand the implementation details, it’s time to start running some tests.

Before you dive deep into the topic of widget testing with Flutter, take a step back and compare it with unit testing.

Unit Testing vs. Widget Testing

Unit Testing vs Widget Testing

Unit testing is a process where you check for quality, performance or reliability by writing extra code that ensures your app logic works as expected. It tests for logic written in functions and methods. The unit tests then grow and accumulate to cover an entire class and subsequently a huge part of the project, if not all.

The goal of a widget test is to verify that every widget’s UI looks and behaves as expected. Fundamentally, you perform tests by re-rendering the widgets in code with mock data.

This also tells you that if you modify the logic of the app — for example, you change the login validation of the username from a minimum of six characters to seven — then your unit test and widget test may both fail together.

Tests lock down your app’s features, which help you to properly plan your app’s design before developing it.

Testing Pyramid

There are three types of tests you can perform with Flutter:

  • Unit tests: Used to test a method or class.
  • Widget tests: These test a single widget.
  • Integration tests: Use these to test the critical flows of the entire app.

So, how many tests will you need? To decide, take a look at the testing pyramid. It summarizes the essential types of tests a Flutter app should have:

Testing Pyramid

Essentially, unit tests should cover most of the app, then widget tests and, lastly, integration tests.

Even when good testing grounds are in place, you shouldn’t omit manual testing.

As you go up the pyramid, the tests get less isolated and more integrated. Writing good unit tests help you build a strong base for your app.

Now that you understand the need for testing, it’s time to dive into the project for this tutorial!

Widget Testing the Car List

Open test/list/cars_list_bloc_test.dart. Look below // TODO 3: Unit Testing Data Loading Logic and you’ll see the unit tests implemented in this project. These unit tests ensure that the data structure you provide to the widget is accurate.

Before going into writing the test scripts, it’s good to look at the actual screen you’re testing. In test/database/mock_car_data_provider.dart, the user has selected the first car — the Hyundai Sonata 2017, shown the image below:

Car List with selected card highlighted in blue

Are you ready to start adding widget tests?