How to Create Unit Tests in Flutter using Dart
Unit testing is an essential part of the software development process that helps ensure the reliability and correctness of your code. In Flutter, unit testing plays a crucial role in verifying the functionality of individual units of code, such as functions or methods. By writing unit tests, you can catch bugs early, improve code quality, and enhance the overall stability of your application.
In this post, we will explore the process of creating unit tests in Flutter, using an example of an AuthenticationPage
widget with a validate
function. We will walk through the setup, write and run the tests, and discuss best practices.
1. Setting up
We will start by setting up our main.dart
file. In this file, we define our MyApp
widget, which extends a StatelessWidget
and sets up our MaterialApp
with the AuthenticationPage
as the home screen, see the code below:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Unit test demo',
home: AuthenticationPage(),
);
}
}
class AuthenticationPage extends StatelessWidget {
const AuthenticationPage({super.key});
String? validate({required String? value, required String field}) {
if (value!.isEmpty) {
return 'This field cannot be empty.';
}
if (field == 'email' &&
RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value) == false) {
return 'This is not a valid email address.';
}
if (field == 'password' && value.length < 6) {
return 'The password must be at least 6 characters.';
}
return null;
}
@override
Widget build(BuildContext context) {
return const Scaffold();
}
}
The AuthenticationPage
widget contains a validate
function responsible for validating input values for email
and password
fields. The function checks for empty fields, validates email addresses using a regular expression and ensures that the password has a minimum length of six characters.
2. Writing our Unit Tests
Let us start by creating our test file. A good start is to create a unit
directory inside your test
directory. Inside the unit
directory, we want to create our test file. We take the name of our function validate
and suffix it with _test.dart
to create a file with the following name validate_test.dart
.
|-- test/
|-- unit/
|-- validate_test.dart
Inside our test file, we will start with the main
function. Inside the main
function, we will write our tests. For unit tests, we use the test
function. Inside the test
function, we provide the name of the test
as the first parameter and a callback in the second parameter. In order to access the test
function we need to import flutter_test.dart
. See the code below:
import 'package:codeonwards_demo/main.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('it returns an error message when email field is empty', () {
});
}
Usuallyflutter_test
is pre-installed, if this is not the case install it by executing the following command:flutter pub add flutter_test --dev
.
As the name of the test suggests we want to make sure that it will return an error message when the email field is empty.
So for this, we need to call the validate
function with an empty value
and the email
. But first, how are we going to call this function? We have to call this function from the AuthenticationPage
widget so let us create an instance.
import 'package:codeonwards_demo/main.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const authenticationPage = AuthenticationPage();
test('it returns an error message when email field is empty', () {
});
}
We put the authenticationPage
variable above the test because we want to reuse it in other tests.
2.1 Email field Unit Tests
Now that we have our instance of the AuthenticationPage
let us call the validate
function inside the test and make sure that we get the expected output:
import 'package:codeonwards_demo/main.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const authenticationPage = AuthenticationPage();
test('it returns an error message when email field is empty', () {
final actual = authenticationPage.validate(value: '', field: 'email');
expect(actual, 'This field cannot be empty.');
});
}
In this code snippet, we are calling the validate
function with an empty value
and the email
field on line 8. On line 10 we are using the expect
function from the flutter_test
package to assert if our actual output matches what we expect. In this case, we expect the "this field cannot be empty" error message.
With this, we finished our first test. You can run the test in multiple ways. You can execute the flutter test
command inside the CLI, make sure you are inside your project. This command will run all tests. If you are using an IDE like IntelliJ or VSCode they have built-in support to run tests. See the GIF below:

It is also possible to run a single test from the CLI by executing the following command: flutter test --plain-name="it returns an error message when email field is empty"
. As you can see after the --plain-name
flag you need to add the name of the test in double quotes ""
.

Now that we have our first email test down, let us write the other email tests, see the code below:
import 'package:codeonwards_demo/main.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const authenticationPage = AuthenticationPage();
group('email', () {
test('it returns an error message when email field is empty', () {
final actual = authenticationPage.validate(value: '', field: 'email');
expect(actual, 'This field cannot be empty.');
});
test('it returns an error message when email is not valid', () {
final emailMissingDot = authenticationPage.validate(
value: 'codeonwards@testcom',
field: 'email',
);
final emailMissingAt = authenticationPage.validate(
value: 'codeonwardstest.com',
field: 'email',
);
expect(emailMissingDot, 'This is not a valid email address.');
expect(emailMissingAt, 'This is not a valid email address.');
});
test('it returns null when a valid email address is provided', () {
final actual = authenticationPage.validate(
value: '[email protected]',
field: 'email',
);
expect(actual, null);
});
});
}
In the code snippet above we added the additional tests for the email field. As you can see the approach is similar to our first test, the only difference is that we provide different parameters in the validate
function. Of course, because of this we also have different expectations. Also, you might have noticed that in the second test, we have two expectations in one test. This is perfectly fine and you can add as many in a single test as you need.
We also wrapped all our tests in the group
function and named it "email". Grouping tests allow us to run the group as a whole using the --plain-name
flag and provide the name of the group: flutter test --plaine-name="email"
.

2.2 Password Field Unit Tests
The tests for the password are similar to those from the email, however in the group
function we will create a function, see the code below:
group('password', () {
String? validatePassword({required String? value}) {
return authenticationPage.validate(value: value, field: 'password');
}
test('it returns an error message when password field is empty', () {
final actual = validatePassword(value: '');
expect(actual, 'This field cannot be empty.');
});
test('it returns an error message when password has less than 6 characters',
() {
final actual = validatePassword(value: '12345');
expect(actual, 'The password must be at least 6 characters.');
});
test('it returns null when password with 6 characters or more is provided',
() {
final actual = validatePassword(value: '123456');
expect(actual, null);
});
});
In this code snippet, we have added the validatePassword
function in the group
function. Because inside the password group, we will always be setting the field
parameter of the validate
function to password
. By doing this we simplify our code by using a function that already sets the field to password
for us. This is another benefit of grouping tests. The same can be done for the email
field.
Now when we run all our tests using flutter test
you will see that all our tests succeed.

With that, we successfully tested the validate
function of our AuthenticationPage
widget.
3. When to write Unit Tests?
Knowing when to write unit tests is crucial for maintaining a balance between development speed and code quality. Here are some key scenarios where writing unit tests is highly recommended:
- New features or functionalities: When adding new features or functionalities to your application, it's a good practice to write unit tests alongside the implementation. This helps ensure that the new code behaves as expected and does not break existing functionality.
- Bug fixes: Whenever you encounter a bug in your application, it is essential to write a unit test that reproduces the issue. This not only helps in identifying the root cause of the bug but also makes sure the fix does not break any existing functionality.
- Code refactoring: During code refactoring, when you modify or optimize existing code without changing its external behavior, unit tests act as a safety net. They provide confidence that the refactored code still functions correctly and has not introduced any unintended side effects.
- Complex logic or critical components: Unit tests are particularly valuable for complex logic or critical components of your application. By thoroughly testing these parts, you can ensure that they work as intended and handle various edge cases.
In general, it is beneficial to adopt a test-driven development (TDD) approach, where you write tests before writing the implementation code. This ensures that your code is testable, promotes clear requirements, and helps maintain a high level of test coverage.
Flutter tests can also be run with coverage, this way you know exactly which lines of code are being tested, see the post below:

4. Conclusion
Unit testing is a fundamental practice in software development, and Flutter provides excellent support for writing and executing unit tests. In this post, we explored the process of creating unit tests for a Flutter application, focusing on the example of an AuthenticationPage
widget's validate
function.
We started by setting up the main.dart
file and defining the widget structure. Then, we proceeded to write unit tests using the flutter_test
package. We covered scenarios such as empty email fields, invalid email addresses, and password validation.
Remember, unit tests are not a one-time effort but an ongoing process. Regularly maintaining and updating your tests as your codebase evolves is crucial for long-term success. By investing time in writing comprehensive unit tests, you can ensure the reliability, maintainability, and stability of your Flutter applications.