Flutter Test Coverage

03 / Jul / 2022 by saurabh.sablok 0 comments

As a good practice we should always write unit tests while writing the code for a software. Next, it is important to measure that the test we write is enough for our code or not, as per our coverage criteria.

We leverage a few tools, discussed below, that check which lines of the codes are not covered in the tests.

Pre-requisite (basic tools) required to do test Coverage:

  • IDE – Android Studio or VS code
  • Framework – flutter
  • Lcove
  • Knowledge of writing tests

Following is the command to run test in flutter:

flutter test 

This command will run all test files under main /test folder in root project. If we want to generate coverage also then we have to run the following command: 

Flutter test —coverage

Test with coverage will create coverage folder in root project with a file named lcov.info. This file has all the info related to test coverage of each file.

There is an issue in flutter test coverage, it wouldn’t report untested files in lcov. There is a work around to include all in lcove test coverage report. We have to write a script to import all dart files excluding generated files like *.g.files, freezed files etc. You can see the issue in detail here: https://github.com/flutter/flutter/issues/27997

Going ahead, we will also create file import_file_coverage.sh

printf “// Helper file to make coverage work for all dart files\n” > $file
printf “// **************************************************************************\n” >> $file
printf “// Because of this: https://github.com/flutter/flutter/issues/27997#issue-410722816\n” >> $file
printf “// DO NOT EDIT THIS FILE USE: sh scripts/import_files_coverage.sh YOUR_PACKAGE_NAME\n” >> $file
printf “// **************************************************************************\n” >> $file
printf “\n” >> $file
printf “// ignore_for_file: unused_import\n” >> $file
find lib \
‘!’ -path ‘*generated*/*’ \
‘!’ -iname ‘*.g.dart’ \
‘!’ -iname ‘*.freezed.dart’ \
‘!’ -iname ‘*.part.dart’ \
‘!’ -iname ‘generated_plugin_registrant.dart’ \
-iname ‘*.dart’ \
| cut -c4- | awk -v package=”${1}” ‘{printf “import ‘\”package:%s%s’\”;\n”, package, $1}’ >> $file
printf “\nvoid main(){}” >> $file

This file (import_file_coverage.sh) needs a package name as parameter. We add the package name and run command as follows:

sh import_file_coverage.sh your_package_name

This will generate coverage_helper_test.dart file under test folder and add all these files in lcov.info to generate report against all these files.

Now our next task is to create readable test coverage report out of lcove.info file. To do this we will take help of genhtml command which will generate readable html file that would open in your default browser:

Bravo, now we have line coverage of each folder and file! 

When we click on any of the file in the above image, it will take us to a detailed coverage of MyHomePage.dart file like below:

In the above files the blue lines means lines covered and red means non tested lines. As we can see in MyHomePage.dart file we have covered 93.3%, with two line remain untested. We can also cover these lines by writing tests for these lines.

But there is one challenge here. In models we see there is only 70.8% coverage (top right in red colored cell). This is because it has also covered the generated files for which we cannot write test case. Thus, we will remove all *.g.dart files and other generated files from our generated lcove file. Here is the command to remove generated files:

lcov –remove coverage/lcov.base.info \
 ‘lib/main.dart’ \
‘lib/*/*.freezed.dart’ \
‘lib/*/*.g.dart’ \
‘lib/*/*.part.dart’ \
‘**/generated/*.dart’ \
‘**/generated/*/*.dart’ \
‘**/gen/*.dart’ \
‘**/gen/*/*.dart’ \
-o coverage/lcov.base.info

Now, the whole script will look like this:

File create_clean_lcov_and_generate_html.sh

set -e
flutter pub get
sh scripts/import_files_coverage.sh your_package_name
flutter test –coverage
lcov –remove coverage/lcov.base.info \
  ‘lib/main.dart’ \
  ‘lib/*/*.freezed.dart’ \
  ‘lib/*/*.g.dart’ \
  ‘lib/*/*.part.dart’ \
  ‘**/generated/*.dart’ \
  ‘**/generated/*/*.dart’ \
  ‘**/gen/*.dart’ \
  ‘**/gen/*/*.dart’ \
  -o coverage/lcov.base.info
genhtml coverage/lcov.info -o coverage // will generate readable html file from lcov.info file
open coverage/index.html

And finally we need to extract total test coverage percentage from lcov.info file that is covered, for that we will write following command:

coverageRate=$(lcov –summary “coverage/lcov.base.info” | grep “lines……” | cut -d ‘ ‘ -f 4 | cut -d ‘%’ -f 1)

REQUIRED_COVERAGE_PERC=80 // You can define the percentage you want to cover, in this case we defined 80% 

After getting the percentage we can use this in any CI to exit the build process if it is not meeting our minimum code coverage criteria or percentage. Here is the following code to do the same:

coverageRate=$(lcov –summary “coverage/lcov.base.info” | grep “lines……” | cut -d ‘ ‘ -f 4 | cut -d ‘%’ -f 1)
REQUIRED_COVERAGE_PERC=80 // You can define the percentage you want to cover, in this case we defined 80% 

if [ “$(echo “${coverageRate} < $REQUIRED_COVERAGE_PERC” | bc)” -eq 1 ]; then
    printf “coverage is too low, We required ${REQUIRED_COVERAGE_PERC}% and have ${coverageRate}%.\n”
    exit 1
    printf “Coverage rate is up to mark.\n”

I hope this blog helps you understand how you can measure flutter code coverage and if it meets our minimum coverage criteria.

Stay tuned for Part 2 of this blog that will cover “How to test sub-packages of flutter project”.


Leave a Reply

Your email address will not be published. Required fields are marked *