Testing Flutter Applications

Testing is one of the most important things during the software development. As your product gets bigger, it will be harder to test everything manually. In this scenario, having a testing environment would make more sense.
Flutter has 3 types of tests.
- Unit tests are the one used for testing a method, or class.
- Widget tests are the tests for controlling single widget.
- Integration tests are tests the large scale or all of the application.
Now we will cover all of these in detail. Of course, we will need a project to test and luckily we have implemented a flutter project before in the Flutter in a Weekend blog post series. You can also download the project below.
If you are ready, let’s get started!
Unit Tests
Unit tests are there to test the logic of your application.e.g. to test a method or a class behaviour. Mostly they do not interact with user input, they do not read or write data from the disk and most importantly they do not use external dependencies by default. When the external dependencies are needed, it can be mocked with packages like Mockito.
While writing unit tests we are using package:test
package. This package gives us the necessary files to test the application on Dart VM with flutter test
command.
We will provide a simple response to our unit test. We will test createMovieObject
method in MovieList.dart
to check if the object is created correctly. We will also test the createMovieList
method to check if it creates the movie list correctly.
import 'package:flutter_app/Movie.dart';
import 'package:flutter_app/MovieList.dart';
import 'package:test/test.dart';
void main() {
MovieList movieList;
String jsonText = '{' +
'"page": 1,' +
'"total_results": 7104,' +
'"total_pages": 356,' +
'"results": [{' +
'"vote_count": 1296,' +
'"id": 19404,' +
'"video": false,' +
'"vote_average": 9.2,' +
'"title": "Dilwale Dulhania Le Jayenge",' +
'"popularity": 15.99626,' +
'"poster_path": "/uC6TTUhPpQCmgldGyYveKRAu8JN.jpg",' +
'"original_language": "hi",' +
'"original_title": "Dilwale Dulhania Le Jayenge",' +
'"genre_ids": [' +
'35,' +
'18,' +
'10749' +
'],' +
'"backdrop_path": "/nl79FQ8xWZkhL3rDr1v2RFFR6J0.jpg",' +
'"adult": false,' +
'"overview": "Raj is a rich, carefree, happy-go-lucky second generation NRI. Simran is the daughter of Chaudhary Baldev Singh, who in spite of being an NRI is very strict about adherence to Indian values. Simran has left for India to be married to her childhood fiancé. Raj leaves for India with a mission at his hands, to claim his lady love under the noses of her whole family. Thus begins a saga.",' +
'"release_date": "1995-10-20"' +
'}]' +
'}';
setUp(() {
movieList = new MovieList();
});
group("movie list creation test", () {
test('create movie item', () {
List results = movieList.getResultsList(jsonText);
expect(results.length, 1);
Movie movie = movieList.createMovieObject(results[0]);
expect(movie, isNot(null));
expect(movie.originalTitle, "Dilwale Dulhania Le Jayenge");
});
test('create movie list with json string', () {
List<Movie> list = movieList.createMovieList(jsonText);
expect(list.length, 1);
});
});
}
Let’s deeply investigate the code below. As we see, we have a main
method. The main method is the place that your code starts to work. First variable is the example result which is taken from the http://api.themoviedb.org/3/movie/top_rated?api_key=<your-api-key>
url.
The first keyword that we are seeing issetUp
. It registers a function to be run before each test run but within a test group, it is called only once. It can run asynchronously and in that case, it can return a Future
object. In our case, it creates a MovieList
object before the test run.
Next, we can see a keyword called group
. This keyword indicates a group of tests. We can use this to categorise the tests around small groups, so we can have more readable tests. We can add a descriptive text to the group to make it self-explanatory.
Afterwards, we can see a keyword called test
. This one is obvious. This is the place where the test happens. We can also add here a descriptive test about what are we achieving or testing within this test.
The last keyword that we will see in the tests, is calledexpect
. It is the main assertion function. If the assertion succeeds it returns true and if it fails it throws a TestFailure
object to describe what is missing.
Now, everything is settled. Let’s run tests by clicking the green arrow can be seen below.

Widget Tests
Widget tests are the tests to check if a widget is created as expected and also to check if widget interactions work as expected.
The creation process of the widget tests is similar to unit tests. Extra to unit test, we use the WidgetTester
utility that Flutter provides for testing the widgets. With this tests, it is possible to send taps, scroll to the certain position etc. in the widget.
In our project, we will test the MovieDetail
widget to learn widget tests. We will test if the passed information to the widget, is shown correctly. If everything is clear, let’s create our test.
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_app/Movie.dart';
import 'package:flutter_app/MovieDetail.dart';
import 'package:flutter_test/flutter_test.dart';
import 'MockImageHttpClient.dart';
void main() {
// Mock object creation
Movie movie = new Movie(
"Dilwale Dulhania Le Jayenge",
"/uC6TTUhPpQCmgldGyYveKRAu8JN.jpg",
"/nl79FQ8xWZkhL3rDr1v2RFFR6J0.jpg",
"Dilwale Dulhania Le Jayenge",
9.2,
"Raj is a rich, carefree, happy-go-lucky second generation NRI. Simran is the daughter of Chaudhary Baldev Singh, who in spite of being an NRI is very strict about adherence to Indian values. Simran has left for India to be married to her childhood fiancé. Raj leaves for India with a mission at his hands, to claim his lady love under the noses of her whole family. Thus begins a saga.",
"1995-10-20");
// Expected widget within created widgets
List<String> expectedTexts = [
"Original Name: " + movie.originalTitle,
"Release Date: " + movie.releaseDate,
movie.voteAverage.toString(),
movie.overview,
movie.title
];
testWidgets('Movie Detail Widget test', (WidgetTester tester) async {
HttpOverrides.runZoned(() async {
await tester.pumpWidget(new MaterialApp(home: new MovieDetail(movie)));
// Gets the created widgets
Iterable<Widget> listOfWidgets = tester.allWidgets;
checkIfTextsCreatedCorrectly(listOfWidgets, expectedTexts);
}, createHttpClient: createMockImageHttpClient);
});
}
/// Method to check if all texts are created as expected.
void checkIfTextsCreatedCorrectly(Iterable<Widget> listOfWidgets, List<String> expectedTexts) {
var textWidgetPosition = 0;
for(Widget widget in listOfWidgets) {
if(widget is Text) {
expect(widget.data, expectedTexts[textWidgetPosition]);
textWidgetPosition++;
}
}
}
If we look at the test code closer, we can see that, as in the previous example, we have created a mock object to pass into our widget.
Next thing that we are doing is to create a list of strings with the expected values.
After this operations, real tests begin. testWidgets
method runs the tests for Stateless and Stateful widgets. Inside testWidgets method, we can see an object called WidgetTester
. It’s a class that programmatically interacts with widgets and the test environment.
Because of a known issue in Flutter widget tests, all NetworkImage calls are returning 400
and because of that, we need to override the calls to get the Image. For fixing this, they have provided a workaround with the package:mockito
. For enabling mockito you should add mockito: “^2.2.3”
to your pubspec.yaml
file under dependencies
. What code does; it creates a HttpClient to intercept the call and return a mocked answer for the calls. Since we make only one call in MovieDetail class, it does the required behaviour. We will add this code from this link.
Inside of the testWidgets method, we are able to see a new keyword. It’s called HttpOverrides
. This class helps us to override an HttpClient with a mock implementation. runZoned
method’s first parameter is helping us to assign code block to be working within the overridden implementation and the second parameter is the place that we can pass the mock HttpClient
that we created before.
We are creating our widget by using thepumpWidget
method. Before this method call, we are adding await
keyword so, during the process of creation rest of the code will be on hold.
In the next line, we are getting all the widgets that are created via using our WidgetTester object’s allWidgets
value.
The last thing we do is a method call for checking if the views are showing the right data or not. In the method that we created, we are iterating through all the widgets. For each widget element, we are checking if the widget texts are equal to the expected texts. For the assertion of this check, we are using expect
function again.
Now you can run the tests as you run unit tests.
Integration Testing
Integration tests are the test types that we give instructions to our application to do something and check if the wanted behaviour is happening in the application. Creation process and running the tests are completely different than unit and widget tests. But don’t worry, we will explain everything to get you started.
In the test that we are going to implement in our application, we will wait for the HTTP call to finish, click on one of the items and check if it opens up the correct information.
For creating an integration test, first of all, we need to have Flutter driver in our pubspec.yaml
file. You can add it by adding the following code under dev_dependencies
.
flutter_driver:
sdk: flutter
For starting the test creation process, we should create an instrumented version of application’s start point. For creating that, let’s create a folder called test_driver
. Under this folder, let’s create a file called MovieAppIntegrationTest.dart
and add the code below.
import 'package:flutter_driver/driver_extension.dart';
import 'package:flutter_app/main.dart' as app;
void main() {
// This line enables the extension
enableFlutterDriverExtension();
app.main();
}
What this code does, enables the Flutter driver extension. This extension enables us to create an integration test. After it’s enabled, we can call our main
method to start the application.
For writing the test, we are required to create another file with _test
suffix with the integration test file name, that is created earlier. This file should be also intest_driver
folder. e.g. For the MovieAppIntegrationTest.dart
we should create MovieAppIntegrationTest_test.dart
file.
Let’s add our real test to the file that we created.
// Imports the Flutter Driver API
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';
void main() {
group('scrolling performance test', ()
{
FlutterDriver driver;
setUpAll(() async {
driver = await FlutterDriver.connect();
});
tearDownAll(() async {
if (driver != null) {
driver.close();
}
});
test("Click list item in the list test", () async {
await driver.tap(find.text("Dilwale Dulhania Le Jayenge"));
await driver.waitFor(find.text("Raj is a rich, carefree, happy-go-lucky second generation NRI. Simran is the daughter of Chaudhary Baldev Singh, who in spite of being an NRI is very strict about adherence to Indian values. Simran has left for India to be married to her childhood fiancé. Raj leaves for India with a mission at his hands, to claim his lady love under the noses of her whole family. Thus begins a saga."));
});
});
}
Let’s see what is new in the code that we created. setUpAll
is the first new thing that we are seeing. It’s a helper method to register a function to be run once before all tests. For the same purpose, we also have a function called tearDownAll
. It works after the tests run.
In the setUpAll method, we connect to FlutterDriver
which is the class that we are going to use for user interactions such as typing text, tapping etc. In the tearDownAll method, we will close the connection to VM.
Lastly, we have our test to run. In the test method, we have two instructions for the test. First one is, to click on the text provided, which is the title text of the first movie. The second instruction is to check for the overview text of the related movie.
Now we can run our application. First of all, be sure that you are connected to a device to test. Secondly, use the command below to run the integration test.
flutter drive --target=test_driver/MovieAppIntegrationTest.dart
As you see, the target
is the path of the instrumented version of the test file that we created in the first place. After running this, you could see something like this in your terminal.
muhammedsalihguler@Muhammeds-MBP ,~/development/flutter/flutter_popular_movies (FPM-Testing) $ flutter drive --target=test/MovieAppIntegrationTest.dart
Using device Pixel.
Starting application: test/MovieAppIntegrationTest.dart
Initializing gradle... 1.0s
Resolving dependencies... 0.8s
Installing build/app/outputs/apk/app.apk... 10.8s
Running 'gradlew assembleDebug'... 1.4s
Built build/app/outputs/apk/debug/app-debug.apk (32.0MB).
I/FlutterActivityDelegate(24184): onResume setting current activity to this
I/flutter (24184): Observatory listening on http://127.0.0.1:48109/
00:00 +0: scrolling performance test (setUpAll)
[info ] FlutterDriver: Connecting to Flutter application at http://127.0.0.1:8101/
[trace] FlutterDriver: Looking for the isolate
[trace] FlutterDriver: Isolate is paused at start.
[trace] FlutterDriver: Attempting to resume isolate
[trace] FlutterDriver: Waiting for service extension
[info ] FlutterDriver: Connected to Flutter application.
00:02 +0: scrolling performance test Click list item in the list test
00:04 +1: scrolling performance test (tearDownAll)
00:04 +1: All tests passed!
Stopping application instance.
Conclusion
Wow, we have covered a lot of testing in Flutter related information and how to do testing in the popular movies application that we created before.
If somethings are not clear do not worry. It might take some time all of this information to settle and if you have any questions, do not hesitate the add a comment.
Happy coding!
p.s.You may check the code in the repository below.