Flutter supports three testing types: unit, widget, and integration.

Unit tests prove that your code works, integration — that the app works. And it’s not the same if you think about it. The code logic might not fulfill the business cases, and business cases might not cover all the implementation edge-cases.

Widget tests are in the middle. They are fast, close enough to your codebase, hence it is possible to cover edge-cases easily, and they are focused on the UI, i.e. they are more about business needs than about the code.

When you generate a new project there will be a widget test in it. So why it is ignored in most of the Flutter projects?

This might be because writing tests is a skill which requires some efforts to be developed. You should learn best practices for test structure, code sharing, master test-first or test-last approaches, and so on. You need to learn a lot before you can start testing effectively. Otherwise, you’ll end up with a thought that “it is hard to write tests” or “tests are fragile” which will lead you to the conclusion that “it is not worth it because your clients don’t pay you for tests”.

We often spend some time describing how the app should work to a QA person. What if you’d always have a QA team member with perfect memory, who will (for free and in no time!) make a regression testing of the whole app with every code change based on the description you have provided once?

Let’s imagine how it might be if you could write tests in a natural language in the same manner of just describing what should be tested to a human colleague.

We will call our app features as Features. To test a feature we will describe testing scenarios, and each scenario will consist of one or more steps.

Let’s get our hands dirty and use the default counter app for experiments: create a new file in the test folder called, for example, ‘counter.feature’:

Now add a bdd_widget_test package in dev_dependencies of the pubspec.yaml file. Not sure how to do that? Here is the short manual for you. This plugin relies on code generation provided by build_runner, so add it as well. In the real-world app you might already use it for freezed, injectable, or tons of other awesome plugins.

You may run the code generation with the one of the following commands:

The first command create a test file and a step folder with more files in it. We will review them in a moment. The second command will not only do that but keep tracking changes in feature files.

In any case, now we have widget tests generated from the feature file! Sorry to disappoint you, but we didn’t invent the language for tests. Our feature file is written in Gherkin. That’s the industry standard for BDD (behavior-driven development) approach for testing.

The plugin we used parses feature files and generates test files, which Flutter understands. Let’s review the generated files.

counter_test.dart file is the widget test similar to the one you already have in your project. Our feature name became group name, scenario name became test name, and our steps are functions in corresponding files.

Two other files are test steps. the_app_is_running.dart contains code for the appropriate step and it does exactly what it states — pumps the MyApp widget and prepares it for testing. The last file is i_see_text.dart which uses Flutter widget framework to check whether some string is present on the testing app. The bdd_widget_test framework created a structure for your tests — it separated test scenarios from the exact steps so you may reuse steps in different scenarios. The more test cases you’ll write, the more test steps you’ll implement, and the less efforts it will take to introduce new tests.

You may run the test from counter_test.dart file from the IDE or from the console using the flutter test command.

The test file will be updated with each feature file update, but step files will not be overwritten. As you may see the plugin is smart enough to generate some common steps (“the app is running” and “I see text”), but it’s our responsibility to code custom steps.

Let’s add one more scenario in the feature file:

If you ran the build_runner with watch argument, right after you save the feature file the corresponding Dart file updates and a new step file appears. Otherwise you’ll need to run the command from the above one more time.

We have described how the app should behave in just a plain text and the plugin generated the same tests the default counter app has! Isn’t that awesome?

The pattern is the same for all apps! You may describe the whole app in plain English and let Flutter validate your codebase and do the regression testing even for the smallest changes.

This approach not only allows you to be confident during the refactoring but show the way to the test-first approach — when you write a test before the real implementation. The development process becomes simpler and faster when you describe what should be implemented and only then start the implementation.

Here is the link to the repository with the counter app and these two basic tests:

If you’re ready to try this approach but not sure how to start you may watch my “BDD in Flutter #2 — adding tests to a completed project” video for an additional sample and tips&tricks:

Thanks for reading!

Mobile developer