Getting started with Gherkin and testing Flutter App in simple steps
Behaviour Driven Development of Flutter Apps using Gherkin
When software is described in terms of how users see it behaving, it becomes easier for all technical and non-technical people to understand it. Flutter based mobile or web applications are no exception to it.
But when it comes to setting up a validation framework around BDD, there is a lot that needs to be done at the beginning, like setting up Gherkin (or any other spec framework), glue definition setup, etc., which could be time-consuming and costly.
The Flutter community has built some libraries to help you achieve this with minimal boilerplate code.
However, at Technogise, we have built an open-source library, which will help you set up the BDD based validation framework even faster, and in a better way.
In this blog, we will talk about the open-source library flutter_gherkin_addons built by Technogise.
More about Gherkin
Gherkin is a Format and Domain Specific Language for writing Cucumber specifications. It serves the purpose of documenting user scenarios as well as writing automated tests (for Behaviour Driven Development). More about Gherkin can be read here.
About flutter_gherkin_addons
While exploring various alternatives for writing gherkin style tests in Dart language with flutter, we found a very good library flutter_gherkin that has many inbuilt features that we can leverage. To make it more simple we built our own library flutter_gherkin_addons on top of it which extends few features provided by the core library.
Some cool features that flutter_gherkin_addons offers:
As we saw above, flutter_gherkin_addons provides some nice features that help us write more robust tests, faster.
- Step definitions as DSL: It has all wrappers for creating step definitions in more DSL format. Because of this, you can write step definitions with very less code. So your code becomes less verbose.
- Convention driven: The glue code required to tie your step definitions is minimal and is based on Convention over Configuration philosophy, which means you only need to write something if you want to alter the default behavior of Test Framework else everything is as per the convention defined by the library.
- Easy to mock API calls: This library has an inbuilt mock server that you can optionally turn on and get your tests independent of API dependencies in minutes.
- Reporting: You don’t need to add anything special for taking screenshots, reports, etc. as it comes bundled with library convention. You just need to turn it on and off in the configuration.
- Extensible: You can very well extend the behavior of the library and framework by writing your abstractions.
Sample App
To get started with Gherkin on Flutter, let’s take an example of a sample Flutter app (code can be found here) which has the following screens:
- Home Screen that has the option to navigate to All Person Page
- List of Persons Screen that displays a list of all characters from Star Wars
- Person Details Screen when we tap on a person
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 ListThen 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 ListThen 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.