JGiven BDD Testing Framework

repository·master·Indexed 19 days ago

https://github.com/tng/jgiven

A Behavior-Driven Development (BDD) testing framework for Java that allows developers to write scenarios directly in Java code using a fluent API. It generates readable Given-When-Then reports in HTML, HTML5, and AsciiDoc formats. The framework supports integration with JUnit 5, TestNG, Spring Boot, Selenium, and Android via the Espresso testing framework.

Tokens
17.7K
Snippets
66
Records
83
Agent score
63%

What's inside JGiven

  1. What is JGiven and why use it?

    master

    JGiven is a Behavior-Driven Development (BDD) tool for Java designed to bridge the gap between developer-friendly test code and readable behavioral documentation.

    Unlike other BDD tools, JGiven allows developers to write scenarios directly in Java, minimizing the maintenance overhead associated with external plain-text (Cucumber, JBehave) or HTML-based (Concordion, Fitness) scenario files. This approach provides:

    • Developer-friendliness: Scenarios are written in Java, making them as easy to maintain as standard JUnit tests.
    • Living Documentation: JGiven automatically generates behavioral documentation from the test code that can be read and reviewed by non-developers like business analysts and testers.
    • Modularity: Encourages clean, readable, and modular test code structures.
  2. Overview of the JGiven HTML5 Report

    master

    The JGiven HTML5 Report is a dynamic reporting alternative to the standard static HTML report. It leverages AngularJS, Foundation, and Font Awesome to provide a more interactive user experience.

    Key Advantages

    • Reduced Storage Footprint: Instead of generating numerous HTML files for every tag and class, it generates a single JSONP file used as the data input.
    • Dynamic Interactivity: Being a dynamic report, it offers additional features and interactivity that are not present in the traditional static HTML report.
  3. Overview of JGiven modules

    master

    JGiven is composed of several modules depending on your testing environment and reporting needs:

    • JGiven Core: The essential implementation required for all JGiven usage.
    • JGiven JUnit: Integration for JUnit 4.x.
    • JGiven JUnit 5: Experimental integration for JUnit 5.x.
    • JGiven TestNG: Integration for the TestNG framework.
    • JGiven Spring: Integration for the Spring framework, allowing Stage classes to be treated as Spring beans.
    • JGiven Android: Experimental support for executing tests on Android devices or emulators.
    • JGiven HTML5 Report: A tool to generate visual HTML5 reports from the JSON files produced during test execution.
  4. What is JGiven and how does it work?

    master

    JGiven is a developer-friendly BDD (Behavior-Driven Development) tool for Java. Unlike classical BDD tools (like Cucumber or JBehave) that require writing scenarios in plain text files and binding them to code via regular expressions, JGiven allows developers to write scenarios directly in standard Java code using a fluent, domain-specific API.

    Key characteristics:

    • Plain Java: Scenarios are written in standard Java without needing extra languages (like Groovy or Scala) or IDE plugins.
    • No Annotations: Java method names and parameters are parsed during execution to build the scenario description.
    • Standard Runners: Scenarios are executed using standard JUnit or TestNG runners.
    • Modular Stages: Scenarios are composed of "stages" that share state via injection, allowing for modular test design.
    • Readable Reports: JGiven generates Given-When-Then reports (Plain Text, HTML, etc.) that are readable by domain experts and business owners.
  5. What is JGiven and how does it work?

    master

    JGiven is a lightweight Java library designed to help you create a high-level, domain-specific language (DSL) for writing Behavior-Driven Development (BDD) scenarios.

    Unlike monolithic testing frameworks, JGiven follows a Unix-like philosophy: it focuses solely on providing a readable abstraction layer for your scenarios. It does not replace your existing testing stack; instead, you continue to use your preferred assertion and mocking libraries for the actual test implementations, while using JGiven to structure those implementations into readable BDD steps.

  6. Reuse setup/teardown logic with @ScenarioRule

    master

    To avoid duplicating @BeforeScenario and @AfterScenario logic across multiple stages, use a Scenario Rule. A scenario rule is a separate class that provides before() and after() methods.

    This pattern is compatible with JUnit's ExternalResource, allowing you to use classes like TemporaryFolder directly as a rule. Once defined, the rule can be reused in multiple stages.

  7. Implement fluent subclassing of stages using Generics

    master

    When subclassing a stage, standard return this; calls will return the parent class type, breaking the fluent chain for the subclass. To fix this, use a generic type parameter SELF in the parent class and return self() (provided by the Stage class) instead of this.

    // Parent class using Generics
    public class GivenCommonSteps<SELF extends GivenCommonSteps<SELF>> extends Stage<SELF> {
        public SELF my_common_step() {
            return self();
        }
    }
    
    // Subclass
    public class GivenSpecialSteps extends GivenCommonSteps<GivenSpecialSteps> {
        public GivenSpecialSteps my_special_step() {
            return self();
        }
    }
    
    // Usage: Now you can chain both
    // given_special_steps.my_special_step().my_common_step();
  8. Share state between stages using @ScenarioState

    master

    To share data between different stage classes, define a field with the same name and type in both the providing stage and the consuming stage. Both fields must be annotated with @ScenarioState.

    Resolution Strategies

    • Type Resolution (Default): Fields are matched by their type. You can only have one field of a specific type shared this way.
    • Name Resolution: Use @ScenarioState(resolution = Resolution.NAME) to match fields by their variable name instead of type. This is useful if you need multiple fields of the same type (e.g., two different String objects).
    • Exceptions: Types in java.lang.* and java.util.* are resolved by name by default.

    Validation

    Use @ScenarioState(required = true) (or @ExpectedScenarioState(required = true)) to force JGiven to validate that a value was actually provided by a previous stage. If not, an exception is thrown.

    // In the providing stage
    public class GivenIngredients extends Stage<GivenIngredients> {
        @ScenarioState
        public List<String> ingredients = Arrays.asList("egg", "milk");
    }
    
    // In the consuming stage
    public class WhenCook extends Stage<WhenCook> {
        @ScenarioState
        public List<String> ingredients;
    }
  9. Use tags with dynamic values

    master

    Instead of creating a new annotation for every unique tag, you can define an annotation with a value() method. JGiven treats different values as distinct tags.

    • Basic Values: An annotation like @Story("ACME-123") will result in a tag named ACME-123.
    • Prepend Type: If you set prependType = true in the @IsTag annotation, the tag name will include the annotation type, e.g., Story-ACME-123.
    • Array Values: You can define the value as a String[] to apply multiple tags of the same type to a single scenario.
    • Explode Array: By default, each element in a String[] value becomes a separate tag. If you set explodeArray = false, all values are combined into a single comma-separated tag (e.g., ACME-123,ACME-456).
    @IsTag(prependType = true)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Story {
        String[] value();
    }
    
    // Usage
    @Test
    @Story({"ACME-123", "ACME-456"})
    public void scenario_with_multiple_stories() {
      ...
    }
  10. How JGiven handles exceptions

    master

    By default, JGiven captures all exceptions thrown within step methods. This allows JGiven to continue executing subsequent steps in the scenario, marking them as 'skipped' in the final report. Once the entire scenario has finished executing, JGiven rethrows the original exception to ensure the test runner (like JUnit) correctly identifies the test as a failure.

    Important Note for TestNG users: If you are using TestNG as your test runner, JGiven does not catch exceptions. This is due to compatibility issues between JGiven's rethrowing mechanism and the TestNG execution model.

  11. Understand the JGiven Scenario Life-Cycle

    master

    A JGiven scenario follows a specific execution order involving stage instantiation, rule execution, and annotation-driven setup/teardown. Understanding this order is critical for knowing when scenario state fields are available for use.

    Execution Order:

    1. An instance for each stage class is created.
    2. The before() methods of all scenario rules of all stages are called.
    3. The @BeforeScenario-annotated methods of all stages are called.
    4. For each stage:
      • Values are injected into all scenario state fields.
      • The @BeforeStage-annotated methods of the stage are called.
      • The steps of the stage are executed.
      • The @AfterStage-annotated methods of the stage are called.
      • The values of all scenario state fields are extracted.
    5. The @AfterScenario-annotated methods of all stages are called.
    6. The after() methods of all scenario rules of all stages are called.

    Integration with Test Frameworks:

    • JUnit4: JGiven's before() and after() methods are called later than JUnit's counterparts. Specifically, JGiven's @After executes before an @AfterScenario or a rule's after() method.
    • JUnit5 and TestNG: JGiven's before() executes later than the framework's before method, and JGiven's after() executes ahead of the framework's counterparts.