๐Ÿš€ BDD Flutter

pub package style: very good analysis License: MIT Buy Me A Coffee

A powerful Flutter package that simplifies Behavior Driven Development (BDD) by automatically generating test files from Gherkin feature files. Write expressive tests in plain English using Given/When/Then scenarios and let BDD Flutter handle the boilerplate code generation.

โœจ Features

  • ๐Ÿ“ Parse .feature files written in Gherkin syntax
  • โšก Generate boilerplate test files automatically
  • ๐Ÿงช Support for both widget tests and unit tests
  • โš™๏ธ Configurable test generation

๐Ÿ“ฆ Installation

Add the following dependencies to your package's pubspec.yaml file:

dev_dependencies:
  bdd_flutter: any
  build_runner: any

๐Ÿš€ Quick Start

  1. Create a .feature file in your project:
Feature: Counter
  Scenario: Increment
    Given I have a counter with value 0
    When I increment the counter by <value>
    Then the counter should have value <expected_value>
    Examples:
      | value | expected_value |
      | 1     | 1              |
      | 2     | 2              |
      | 3     | 3              |
  1. Generate test files:
flutter pub run build_runner build
  1. Run your tests:
flutter test

๐Ÿ’ก Recommendations

When working with generated test files, follow these best practices:

  1. Generated Files:

    • Generated files will have the .g.dart extension (e.g., counter_test.bdd_test.g.dart or counter_scenarios.g.dart)
    • After implementing your tests, it's recommended to:
      • Remove the .g extension from the file name
      • Add an ignore decorator to your feature file (@ignore)
    • This will prevent the generated files from being overwritten by subsequent builds
  2. Feature File Ignore: Add this comment at the top of your feature file:

    @ignore
    Feature: Counter
    

This approach ensures that:

  • Your implemented tests won't be overwritten by subsequent builds
  • Generated files are properly ignored in version control
  • You maintain a clean project structure

โš™๏ธ Configuration

Configure test generation in your build.yaml:

targets:
  $default:
    builders:
      bdd_flutter|bdd_test_builder:
        options:
          generate_widget_tests: false # Default: true
          enable_reporter: true # Default: false
          ignore_features:
            - "test/features/ignored.feature"
            - "test/features/another_ignored.feature"

Configuration Options

Option Type Default Description
generate_widget_tests bool true Generate widget tests when true, unit tests when false
enable_reporter bool false Enable/disable test reporter
ignore_features List [] List of feature file paths to ignore during generation

๐Ÿท๏ธ Decorators

Control test generation with decorators:

Decorator Scope Description
@unitTest Feature, Scenario Generate unit test (overrides config)
@widgetTest Feature, Scenario Generate widget test (overrides config)
@className("CustomName") Scenario Generate custom class name
@enableReporter Feature Enable test reporter
@disableReporter Feature Disable test reporter

๐Ÿ’ก Decorators follow a hierarchy: Scenario-level decorators override Feature-level ones.

๐Ÿ“ Complete Example

1. Feature File (counter.feature)

Feature: Counter
  Scenario: Increment
    Given I have a counter with value 0
    When I increment the counter by <value>
    Then the counter should have value <expected_value>
    Examples:
      | value | expected_value |
      | 1     | 1              |
      | 2     | 2              |
      | 3     | 3              |

2. Generated Files

counter_scenarios.dart

import 'package:flutter_test/flutter_test.dart';

class IncrementScenario {
  static Future<void> iHaveACounterWithValue0(WidgetTester tester) async {
    // TODO: Implement Given I have a counter with value 0
  }

  static Future<void> iIncrementTheCounterBy(WidgetTester tester, dynamic value) async {
    // TODO: Implement When I increment the counter by <value>
  }

  static Future<void> theCounterShouldHaveValue(WidgetTester tester, dynamic expected_value) async {
    // TODO: Implement Then the counter should have value <expected_value>
  }
}

counter_test.dart

import 'package:flutter_test/flutter_test.dart';
import 'counter_scenarios.dart';

void main() {
  group('Counter', () {
    testWidgets('Increment', (tester) async {
      await IncrementScenario.iHaveACounterWithValue0(tester);
      // Example with values: 1, 1
      await IncrementScenario.iIncrementTheCounterBy(tester, '1');
      await IncrementScenario.theCounterShouldHaveValue(tester, '1');
      // Example with values: 2, 2
      await IncrementScenario.iIncrementTheCounterBy(tester, '2');
      await IncrementScenario.theCounterShouldHaveValue(tester, '2');
      // Example with values: 3, 3
      await IncrementScenario.iIncrementTheCounterBy(tester, '3');
      await IncrementScenario.theCounterShouldHaveValue(tester, '3');
    });
  });
}

๐Ÿค Contributing

We welcome contributions! Please feel free to:

  • Open an issue
  • Submit a pull request
  • Share your feedback

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

Libraries

bdd_flutter