Getting started with Gherkin and testing Flutter App in simple steps
By: Atmaram Naik
Tags: Flutter, Bdd Testing, Dart, Gherkin, Technology

Testing Sample Application

The simplest test that can be written to test the Home Page and Person List Page could be about verifying whether all the persons displayed on Person List Page are proper.

In Gherkin style, you would write that test as (for simplicity, I’m just considering first 3 personas)

Given Below Personas exists | name | | Luke Skywalker | | C-3PO | | R2-D2 | When I navigate to Persona List Then I See following Personas | name | | Luke Skywalker | | C-3PO | | R2-D2 |

Writing Some Code

The above is just a specification part of your test so far. We still don’t have an actual implementation of the steps that we have written above. So let’s get started with implementing these steps in Flutter.

While exploring various options for Gherkin style of writing specification implementation, we came across a very good Flutter library flutter_gherkin (by Jon Samwell) that helps us write these steps.

On top of that, we created our flutter_gherkin_addons (by Technogise Pvt. Ltd.) repository to extend and simplify the behavior of this package. Throughout this blog, we will use these two packages and their features.

Add above mentioned two packages in dev_dependencies section of your Flutter application as below:

dev_dependencies:

flutter_gherkin: ^1.0.4

flutter_gherkin_addons: ^0.1.4

2. Create a directory test_driver in the root of your application.

3. Create directory features in test_driver directory and add file ‘homepage.feature’ in this with below contents:

Given Below Personas exists | name | | Luke Skywalker | | C-3PO | | R2-D2 | When I navigate to Persona List Then I See following Personas | name | | Luke Skywalker | | C-3PO | | R2-D2 |

4. Create a file ‘config.yaml’ inside this directory with the following contents:

reporting: true

stubbing: true

5. Now create file app.dart in the same directory with the following contents:

import 'dart:io';

import 'package:persona/main.dart'; <This is your applications main file>

import 'package:flutter/widgets.dart';

import 'package:flutter_driver/driver_extension.dart'; void main() { enableFlutterDriverExtension(); runApp(PersonaApp('local')); <This is place where you initialize application> }

6. Create another file app_test.dart in the same folder with below contents:

import 'dart:async';

import 'package:flutter_gherkin_addons/wrapper.dart';

import './steps/details_page_steps.dart';

import './steps/home_page_steps.dart';

import 'steps/mocking_steps.dart'; Future<void> main() async { return TestRuntime.start( [ //Step definitions ] ); }

Let us understand what the above code does.

app.dart file will be used by the flutter_gherkin package for initializing your application when you start any test. Remember that this will be done automatically.

app_test.dart file bootstraps your Gherkin environment with the main code that calls TestRuntime.start which accepts the array with step definitions.

Now, let’s write some step definitions.

Create directory steps in the test_driver directory. Create file home_page_steps.dart in this directory. Write the following code in it:

GenericGiven1 givenFollowingUserExists(){

return given1("Below Persona exists",(context,Table dataTable) async { // Code for ingesting data in application }); }

4. And add a call to an above function in app_test.dart as below

import 'dart:async';

import 'package:flutter_gherkin_addons/wrapper.dart';

import './steps/details_page_steps.dart';

import './steps/home_page_steps.dart';

import 'steps/mocking_steps.dart'; Future<void> main() async {

return TestRuntime.start(

[

givenFollowingUserExists();

] );

}

Let us understand what we did.

We created a step definition Below Persona exists and let our framework know about this given step.

Notice that we used the ‘given1’ function. This is a wrapper DSL function to create Gherkin steps in a single go. The ‘1’ in ‘given1’ here stands for step with 1 parameter.

Similarly, there are given2, given3… given5 wrappers available.

Similarly, you can create other step definitions like below:

1. I navigate to Persona List

GenericWhen whenINavigateToPersonaList(){ return when("I navigate to Persona List", (context) async { final locator = find.byValueKey("persona"); FlutterDriverUtils.tap(context.world.driver, locator,timeout: context.timeout);

});

}

2. I See following Personas

GenericThen1 thenISeeFollowing(){ return then1("I See following Personas",(context,Table dataTable) async { int index=0; for (var row in dataTable.rows) { final locator = find.byValueKey("card-"+index.toString()); index = index+1; context.expectMatch(await FlutterDriverUtils.getText(context.world.driver, locator, timeout:context.timeout), row.columns.elementAt(0)); } }); }

To run above test just run file app_test.dart from the context menu (make sure you have at least one virtual device running)

Some More Action With Data Mocking

If you noticed throughout this blog, we have been testing our application end to end. In case you just want to test the UI of your application and a mock API call, flutter_gherkin_addon ships with an inbuilt stubbing component that runs as a standalone server on port 8081.

Let’s now prepare our data in the “Below Persona exists” step. We would rewrite the step as:

GenericGiven1 givenFollowingUserExists(){ return given1("Below Persona exists",(context,Table dataTable) async { var persons=[]; for (var row in dataTable.rows) { persons.add(Person( name: row.columns.elementAt(0) )); } TestRuntime.addStub(StubFor.staticHttpGet("/people",Response(200,json.encode({ "results":persons }),headers: {"Content-Type":"application/json"}))); }); }

The above step will make available data on http://localhost:8081/people (in iOS) and http://10.0.2.2:8081/people (Android).

You need to inject this URL in your application when you initialize the application (in the app.dart).

This is optional and you can disable mocking by setting stubbing=false in config.yaml

Closing thoughts

With recent developments in the software world, software products are becoming more and more complex in terms of the system as a whole. Therefore, it becomes crucial how you define the behavior of your product and how you test it against that behavior.

The software community also acknowledges this shift and is actively coming up with its solutions to solve these problems.

As a strong advocate of, and active contributor to, open-source, Technogise is also solving this problem with its open-source library flutter_gherkin_addons.

Please feel free to hack around and share your feedback and suggestions, which can help us improve upon this library.