Flutter Gherkin

a dhiyaul asikin
5 min readFeb 24, 2020

Hi, i will give information automation to Flutter gherkin.

flutter_gherkin #

A fully featured Gherkin parser and test runner. Works with Flutter and Dart 2.

This implementation of the Gherkin tries to follow as closely as possible other implementations of Gherkin and specifically Cucumber in it’s various forms.

Available as a Dart package https://pub.dartlang.org/packages/flutter_gherkin

# Comment
Feature: Addition
@tag
Scenario: 1 + 0
Given I start with 1
When I add 0
Then I end up with 1
Scenario: 1 + 1
Given I start with 1
When I add 1
Then I end up with 2

See https://docs.cucumber.io/gherkin/ for information on the Gherkin syntax and Behaviour Driven Development (BDD).

See example readme for a quick start guide to running the example features and app.

The first step is to create a version of your app that has flutter driver enabled so that it can be automated. A good guide how to do this is show here. However in short, create a folder called test_driver and within that create a file called app.dart and paste in the below code.

import '../lib/main.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_driver/driver_extension.dart';
void main() {
// This line enables the extension
enableFlutterDriverExtension();
// Call the `main()` function of your app or call `runApp` with any widget you
// are interested in testing.
runApp(MyApp());
}

All this code does is enable the Flutter driver extension which is required to be able to automate the app and then runs your application.

To get started with BDD in Flutter the first step is to write a feature file and a test scenario within that.

First create a folder called test_driver (this is inline with the current integration test as we will need to use the Flutter driver to automate the app). Within the folder create a folder called features, then create a file called counter.feature.

Feature: Counter
The counter should be incremented when the button is pressed.
Scenario: Counter increases when the button is pressed
Given I expect the "counter" to be "0"
When I tap the "increment" button 10 times
Then I expect the "counter" to be "10"

Now we have created a scenario we need to implement the steps within. Steps are just classes that extends from the base step definition class or any of its variations Given, Then, When, And, But.

Granted the example is a little contrived but is serves to illustrate the process.

This library has a couple of built in step definitions for convenience. The first step uses the built in step, however the second step When I tap the "increment" button 10 times is a custom step and has to be implemented. To implement a step we have to create a simple step definition class.

import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
class TapButtonNTimesStep extends When2WithWorld<String, int, FlutterWorld> {
TapButtonNTimesStep()
: super(StepDefinitionConfiguration()..timeout = Duration(seconds: 10));
@override
Future<void> executeStep(String input1, int input2) async {
final locator = find.byValueKey(input1);
for (var i = 0; i < input2; i += 1) {
await FlutterDriverUtils.tap(world.driver, locator, timeout: timeout);
}
}
@override
RegExp get pattern => RegExp(r"I tap the {string} button {int} times");
}

As you can see the class inherits from When2WithWorld and specifies the types of the two input parameters. The third type FlutterWorld is a special Flutter context object that allow access to the Flutter driver instance within the step. If you did not need this you could inherit from When2 which does not type the world context object but still provides two input parameters.

The input parameters are retrieved via the pattern regex from well know parameter types {string} and {int} explained below. They are just special syntax to indicate you are expecting a string and an integer at those points in the step text. Therefore, when the step to execute is When I tap the "increment" button 10 times the parameters "increment" and 10 will be passed into the step as the correct types. Note that in the pattern you can use any regex capture group to indicate any input parameter. For example the regex RegExp(r"When I tap the {string} (button|icon) {int} times") indicates 3 parameters and would match to either of the below step text.

When I tap the "increment" button 10 times    // passes 3 parameters "increment", "button" & 10
When I tap the "increment" icon 2 times // passes 3 parameters "increment", "icon" & 2

It is worth noting that this library does not rely on mirrors (reflection) for many reasons but most prominently for ease of maintenance and to fall inline with the principles of Flutter not allowing reflection. All in all this make for a much easier to understand and maintain code base as well as much easier debugging for the user. The downside is that we have to be slightly more explicit by providing instances of custom code such as step definition, hook, reporters and custom parameters.

Now that we have a testable app, a feature file and a custom step definition we need to create a class that will call this library and actually run the tests. Create a file called app_test.dart and put the below code in.

import 'dart:async';
import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart';
import 'package:glob/glob.dart';
import 'hooks/hook_example.dart';
import 'steps/colour_parameter.dart';
import 'steps/given_I_pick_a_colour_step.dart';
import 'steps/tap_button_n_times_step.dart';
Future<void> main() {
final config = FlutterTestConfiguration()
..features = [Glob(r"test_driver/features/**.feature")]
..reporters = [
ProgressReporter(),
TestRunSummaryReporter(),
JsonReporter(path: './report.json')
] // you can include the "StdoutReporter()" without the message level parameter for verbose log information
..hooks = [HookExample()]
..stepDefinitions = [TapButtonNTimesStep(), GivenIPickAColour()]
..customStepParameterDefinitions = [ColourParameter()]
..restartAppBetweenScenarios = true
..targetAppPath = "test_driver/app.dart"
// ..tagExpression = "@smoke" // uncomment to see an example of running scenarios based on tag expressions
..exitAfterTestRun = true; // set to false if debugging to exit cleanly
return GherkinRunner().execute(config);
}

This code simple creates a configuration object and calls this library which will then promptly parse your feature files and run the tests. The configuration file is important and explained in further detail below. However, all that is happening is a Glob is provide which specifies the path to one or more feature files, it sets the reporters to the ProgressReporter report which prints the result of scenarios and steps to the standard output (console). The TestRunSummaryReporter prints a summary of the run once all tests have been executed. Finally it specifies the path to the testable app created above test_driver/app.dart. This is important as it instructions the library which app to run the tests against.

Finally to actually run the tests run the below on the command line:

dart test_driver/app_test.dart

To debug tests see Debugging.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

a dhiyaul asikin
a dhiyaul asikin

No responses yet

Write a response