Unit Testing With Flutter: Getting Started

In this Unit Testing with Flutter tutorial, you’ll improve your programming skills by learning how to add unit tests to your apps. By Lawrence Tan 🇸🇬.

4.4 (28) · 1 Review

Download materials
Save for later

So you’re a Flutter developer who wants to improve your skills and write better apps. Great! One way to be a better developer is to apply Unit Testing to your Flutter apps.

Writing unit tests for your Flutter app can help you create resilient and bug-free code.

In this tutorial, you’ll learn how to write unit tests for your flutter app by building and testing a simple shopping cart app. In the process you’ll learn:

  • Testable Architecture (MVVM)
  • Mocking in Dart
  • How to use GetIt for dependency injection
  • How to write unit tests for a Flutter app
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.

What is Unit Testing?

Unit testing is a process where you check quality, performance or reliability by writing extra testing code that ensures your app logic works the way you expect before putting it into widespread use. Picture walking into your favorite apparel shop, taking a trendy shirt and going into the fitting room.

Developer in a fitting room

In that fitting room, you’re performing a unit test. You’re ensuring that your hands can slot through the shirt’s sleeves, the buttons are in place and the style and size match your figure.

Writing unit tests helps you to confidently build new features, minimize bugs and painlessly refactor existing code. It also forces you to write better, more maintainable code that can be easily tested.

Unit testing focuses on testing the logic of your app rather than the actual user interface.

Testable Architecture

Before you can start writing unit tests, you need to structure your code in such a way that you can easily test a single component of your app.

Traditional patterns like Model-View-Controller (MVC) force you to perform testing on the view controller, which is often tightly coupled with the view. That means that instead of testing a single piece of logic, you’re instead testing the entire view in addition to that piece of logic.

One rule of thumb when building a testable architecture is that you should never import any UI-related packages into your unit test suite. If you are importing UI-related packages, it means your unit tests are testing Flutter widgets instead of app logic, which isn’t what you want.

The Model-View-ViewModel (MVVM) architecture separates business and presentational logic to make writing unit tests much easier. The MVVM pattern allows you to transform data models into another representation for a view.

MVVM architecture diagram

For example, in the app you’ll test in this tutorial, you use a view model to transform JSON data about server-side products into a list of objects. This allows the list view to display a list of products without having to be concerned about how the data got there.

All the processing logic resides in view models, which are isolated from the views. Isolating processing logic from views allows you to focus your testing efforts on your implementation logic.

Note: There are many different ways to make your code testable. Follow the style this tutorial covers, then customize it for your use. By the time you’ve gone through the concepts and examples, you’ll be ready to write your own unit tests.

With these concepts equipped, you’re ready to dive into the starter project to see it in action!

Getting Started

To start, download the begin 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.

For this tutorial, you’ll write unit tests for an e-commerce app called ShopNBuy.

ShopNBuy app

ShopNBuy allows users to:

  • View a list of products from Firebase.
  • Add product(s) to a shopping cart.
  • Perform a cart checkout.

That said, before you start writing tests, take a look around the project.

Explore the Begin Project

An explorer in safari clothing

The begin package includes the entire implementation of the app so that you can focus on testing. Take a quick look at the contents of lib to understand what’s there.

Project Structure

The Product class, under models, is a simple model object which represents a shopping list product item.

In services, you’ll find an API class that handles making a network call to fetch a list of products.

Under viewmodels, you’ll find three important classes:

  • A BaseModelclass that inherits from ChangeNotifier, which uses the Observer pattern to notify listeners of any changes in the model. Calling notifyListeners() rebuilds the widget tree, allowing the UI to react as the model is updated.
  • CartModel extends and inherits BaseModel so it can be easily observed by the UI. You’ll flesh out this class later on.
  • Finally, ProductListModel is a container for a list of products.

In helpers, you’ll find important constants in constants.dart. You’ll also see dependency_assembly.dart, where the dependencies are setup using GetIt, a dependency injection package.

The last important piece of code is the BaseView class under ui/views. BaseView is a wrapper widget which contains the provider state management logic. It also contains a model property that you’ll inject as a dependency.

The base widget is wrapped in a ChangeNotifierProvider, which listens to a ChangeNotifier, exposes it to its descendants and rebuilds dependencies whenever you call ChangeNotifier.notifyListeners.

Finally, switch to widgets, where you’ll find a few UI-related widgets that build the Cart Screen and the Product List Screen. Read through them now to understand how they are implemented.

Dependency Injection With GetIt

GetIt Dependency Injection

When writing testable code you’ll find that you need to supply dependencies to the classes that you want to test. That way when you’re writing a unit test you can test the relevant component in isolation. For example, if you’re trying to test an object that makes a network call and then parses the response, you’ll want to make sure that the object isn’t actually making a call over the network, since that would be very slow and brittle. Instead, you’ll often want to inject another object that actually makes the network call into the component so you can provide a fake version in your unit test.

GetIt allows you to register a class and request it from anywhere. Most notable for this app, you can register a view model as a Factory, which returns a new instance every time a view opens.

You can register classes like API which you’ll normally only need one of as a LazySingleton, which will always return the same instance.