1. Introduction

JGiven is a light-weight Java library that helps you to design a high-level, domain-specific language for writing BDD scenarios. In the spirit of Unix tools, it tries to do one thing and do it well, instead of trying to solve all problems. Thus, you still use your favorite assertion library and mocking library for writing your test implementations, but you use JGiven to write a readable abstraction layer on top of it.

1.1. Module Overview

JGiven consists of a couple of modules.

1.1.1. JGiven Core

The JGiven Core module contains the core implementation of JGiven and is always required.

1.1.2. JGiven JUnit

This module provides an integration into JUnit 4.x. If your test-runner of choice is JUnit then you use this module.

1.1.3. JGiven TestNG

Provides an integration into TestNG

1.1.4. JGiven HTML5 Report

Allows you to generate an HTML5 report from the JSON files that JGiven generates during the test execution.

1.1.5. JGiven Spring

Provides an integration into the Spring framework, which basically means that stages classes can be treated as Spring beans.

1.1.6. JGiven Android

Provides support for executing JGiven tests on an Android device or emulator. This is currently in an experimental status.

1.1.7. JGiven JUnit 5

This module provides an integration into JUnit 5.x. This is currently in an experimental status.

2. Installation

JGiven is installed as any other Java library by putting its JAR file(s) into the classpath. Normally you will do that by using one of your favorite build and dependency management tools like Maven, Gradle, or Apache Ant + Ivy. Alternatively you can download the JAR files directly from Maven Central.

Depending on whether you are using JUnit or TestNG for executing tests, you have to use different dependencies.

2.1. JUnit

If you are using JUnit, you must depend on the jgiven-junit artifact. Note that jgiven-junit does not directly depend on JUnit, thus you also must have a dependency to JUnit itself. JGiven requires at least JUnit 4.9, while the recommended version is 4.13.2.

2.1.1. Maven Dependency

<dependency>
   <groupId>com.tngtech.jgiven</groupId>
   <artifactId>jgiven-junit</artifactId>
   <version>1.3.1</version>
   <scope>test</scope>
</dependency>

2.1.2. Gradle Dependency

dependencies {
   testImplementation 'com.tngtech.jgiven:jgiven-junit:1.3.1'
}

2.2. TestNG

If you are using TestNG, you must depend on the jgiven-testng artifact. Note that jgiven-testng does not directly depend on TestNG, thus you also must have a dependency to TestNG itself.

2.2.1. Maven Dependency

<dependency>
   <groupId>com.tngtech.jgiven</groupId>
   <artifactId>jgiven-testng</artifactId>
   <version>1.3.1</version>
   <scope>test</scope>
</dependency>

2.2.2. Gradle Dependency

dependencies {
   testImplementation 'com.tngtech.jgiven:jgiven-testng:1.3.1'
}

2.3. Java Compiler Note

Note that you should compile your test classes with all debugging information (javac -g). Otherwise JGiven cannot obtain the parameter names of step methods and will generate names of the form argX instead.

3. Getting Started

JGiven can be used together with JUnit or TestNG, here we assume you are using JUnit.

3.1. Create a JUnit test class

First of all you create a JUnit test class that inherits from com.tngtech.jgiven.junit.ScenarioTest:

import com.tngtech.jgiven.junit.ScenarioTest;

public class MyShinyJGivenTest
        extends ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> {
}

The ScenarioTest requires 3 type parameters. Each of these type parameters represents a stage of the Given-When-Then notation. Note that there is also the SimpleScenarioTest class that only requires a single type parameter. In that case, all your scenario steps are defined in a single class. For scenarios where you only want to differentiate between steps that modify state (setup in given(), changes in when()) and steps that assert state (then()) you can use the DualScenarioTest which takes two parameters: a combined GivenWhen stage and a single Then stage.

3.2. Create Given, When, and Then classes

To make your class compile, create the following three classes:

import com.tngtech.jgiven.Stage;

public class GivenSomeState extends Stage<GivenSomeState> {
    public GivenSomeState some_state() {
        return self();
    }
}
import com.tngtech.jgiven.Stage;

public class WhenSomeAction extends Stage<WhenSomeAction> {
    public WhenSomeAction some_action() {
        return self();
    }
}
import com.tngtech.jgiven.Stage;

public class ThenSomeOutcome extends Stage<ThenSomeOutcome> {
    public ThenSomeOutcome some_outcome() {
        return self();
    }
}

JGiven does not require you to inherit from the Stage class, however, the Stage class already provides some useful methods like and() and self().

3.3. Write your first scenario

Now you can write your first scenario

import org.junit.Test;
import com.tngtech.jgiven.junit.ScenarioTest;

public class MyShinyJGivenTest
        extends ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> {

    @Test
    public void something_should_happen() {
        given().some_state();
        when().some_action();
        then().some_outcome();
    }
}

3.4. Execute your scenario

The scenario is then executed like any other JUnit test, for example, by using your IDE or Maven:

$ mvn test

3.5. Using JUnit Rules directly instead of deriving from ScenarioTest

Sometimes it is not possible to derive your test class from the ScenarioTest class, because you might already have a common base class that you cannot easily modify. In that case you can directly use the JUnit Rules of JGiven. Instead of providing your stage classes as type parameters, you inject the stages into the test class with the @ScenarioStage annotation.

public class UsingRulesTest {

    @ClassRule
    public static final JGivenClassRule writerRule = new JGivenClassRule();

    @Rule
    public final JGivenMethodRule scenarioRule = new JGivenMethodRule();

    @ScenarioStage
    GivenSomeState someState;

    @ScenarioStage
    WhenSomeAction someAction;

    @ScenarioStage
    ThenSomeOutcome someOutcome;

    @Test
    public void something_should_happen() {
        someState.given().some_state();
        someAction.when().some_action();
        someOutcome.then().some_outcome();
    }
}

Note that your stage classes have to inherit from the Stage class in order to have the given(), when(), and then() methods. You can now also define convenient methods in your test class to get cleaner scenarios if you like:

public GivenSomeState given() {
    return someStage.given();
}

4. Report Generation

4.1. Plain Text Reports

By default JGiven outputs plain text reports to the console when executed. To disable plain text reports set the following Java system property:

jgiven.report.text=false

4.2. JSON Reports

By default JGiven will generate JSON reports into the jgiven-reports/json directory. JGiven tries to autodetect when it is executed by the Maven surefire plugin and in that case generates the reports into target/jgiven-reports/json. To disable JSON report generation set the following Java system property:

jgiven.report.enabled=false

In order to generate HTML reports, JSON reports are required.

4.2.1. Change report directory

If you want to change the jgiven-reports/json directory, respectively target/jgiven-reports/json, set the following Java system property:

jgiven.report.dir=<targetDir>

If JGiven is executed by the Maven surefire plugin, this can be done by the systemPropertyVariables configuration (see: http://maven.apache.org/surefire/maven-surefire-plugin/examples/system-properties.html).

In case HTML Reports are being generated, the source directory for the JSON Reports needs to be set accordingly (see "HTML Report" for more).

4.3. Dry Run

There is a dry run option, which just generates a report without actually executing the code. This might be helpful to generate a test report quickly without having to wait for the tests to be executed.

jgiven.report.dry-run=true

As the tests are not really executed and thus cannot fail, all tests will be reported as successful.

4.4. HTML Report

To generate an HTML report you have to run the JGiven report generator with the html format option. The reporter is part of the jgiven-html5-report module. The report generator can be executed on the command line as follows (assuming that the jgiven-core and the jgiven-html5-report JAR and all required dependencies are on the Java CLASSPATH)

java com.tngtech.jgiven.report.ReportGenerator \
  --format=html \
  [--sourceDir=<jsonreports>] \
  [--targetDir=<targetDir>] \

To see the HTML report in action you can have a look at the HTML report of JGiven itself

4.4.1. Maven

For Maven there exists a plugin that can be used as follows:

<build>
  <plugins>
    <plugin>
      <groupId>com.tngtech.jgiven</groupId>
      <artifactId>jgiven-maven-plugin</artifactId>
      <version>1.3.1</version>
      <executions>
        <execution>
          <goals>
            <goal>report</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <format>html</format>
      </configuration>
    </plugin>
 </plugins>
</build>

You can add the following configuration options (like the format configuration above) to customize the report. All of them are optional.

Table 1. Configuration Options for Reports
Option Description

format

The format of the generated report. Can be html or text. Default: html

title

The title of the generated report. Default: JGiven Report

customCssFile

Custom CSS file to customize the HTML report. Default: src/test/resources/jgiven/custom.css

customJsFile

Custom JS file to customize the HTML report. Default: src/test/resources/jgiven/custom.js

excludeEmptyScenarios

Whether or not to exclude empty scenarios, i.e. scenarios without any steps, from the report. Default: false

outputDirectory

Directory where the reports are generated to. Default: ${project.build.directory}/jgiven-reports/html

sourceDirectory

Directory to read the JSON report files from. Default: ${project.build.directory}/jgiven-reports/json

Now run:

$ mvn verify

HTML reports are then generated into the target/jgiven-reports/html directory. Note that the plugin relies on the existence of the JSON output, so if the property jgiven.reports.enabled was set to false, no output will be generated.

4.4.2. Gradle

There also exists a plugin for Gradle to make your life easier. Add the following plugins section to your build.gradle file or extend the one you have already accordingly:

plugins {
    id "com.tngtech.jgiven.gradle-plugin" version "1.3.1"
}

When using Kotlin, make sure the JGiven Gradle plugin is configured after the kotlin("jvm") plugin.

Alternatively you can configure the plugin as follows:

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "com.tngtech.jgiven:jgiven-gradle-plugin:1.3.1"
    }
}

apply plugin: "com.tngtech.jgiven.gradle-plugin"

Now run:

$ gradle test jgivenTestReport

HTML reports are then generated into the build/reports/jgiven/test/html/ directory. Note that the plugin relies on the existence of the JSON output, so if the property jgiven.reports.enabled was set to false, no output will be generated.

If you want that the HTML report is always generated after the tests have been executed, you can configure the test task in your Gradle configuration file as follows:

test.finalizedBy jgivenTestReport

For additional information about the Gradle plugin refer to https://plugins.gradle.org/plugin/com.tngtech.jgiven.gradle-plugin

4.5. Configuration File

JGiven will optionally load a configuration properties file, defaulting to: jgiven.properties. The path to the configuration can be customized with the system property:

jgiven.config.path

The encoding for the file is assumed to be UTF-8, but can be customized with the system property:

jgiven.config.charset

The following can be defined in the properties file:

jgiven.report.enabled=false
jgiven.report.dir=<targetDir>
jgiven.report.text=false
jgiven.report.text.color
jgiven.report.filterStackTrace=true

Configuration defined via Java system properties will take precedence over values in the configuration file.

5. Stages and State Sharing

A JGiven scenario consists of multiple stages. Typically, there is a stage for each phase of a scenario: a given stage, a when stage and a then stage, however, it is also possible to just use one stage or use arbitrarily many stages. A stage is implemented by a stage class that contains methods representing the steps that can be used in the scenario. The big advantage of this modular concept is that stages can be easily reused by different scenarios. Stage classes will be proxied by JGiven, therefore, they must neither be final, nor sealed.

5.1. Step Methods

A stage class contains multiple step methods that can be called in a scenario and that will appear in the report. Every non-private, non-final, non-static method of a stage class can be used as a step method. There are no further requirements. In particular, no annotations are needed. Step methods should return the this reference so that chaining of calls is possible. In addition, a step method should be written in snake_case so that JGiven knows the correct casing of each word of the step.

The following code shows a valid step method of the WhenCook stage:

public WhenCook the_cook_mangles_everthing_to_a_dough() {
    assertThat( cook ).isNotNull();
    assertThat( ingredients ).isNotNull();

    dough = cook.makeADough( ingredients );
    return this;
}

JGiven removes the underlines so the step will appear as the cook mangles everything to a dough in the report.

5.1.1. Nested Steps

In general, JGiven does not resolve the contents of a step method for reporting. This behavior can be changed by adding the @NestedSteps annotation to a step method. This is useful for grouping a large set of small scale steps into a small set of higher order steps. For instance, the code

@NestedSteps
public NestedStage I_fill_out_the_registration_form_with_valid_values() {
    return I_enter_a_name("Franky")
        .and().I_enter_a_email_address("franky@acme.com")
        .and().I_enter_a_password("password1234")
        .and().I_enter_a_repeated_password("password1234");
}

will result in the report

I fill out the registration form
    I enter a name Franky
    And I enter a email address franky@acme.com
    And I enter a password password1234
    And I enter a repeated password password1234

The nested steps will appear as a collapsible group in the HTML report.

5.2. Overriding the Default Reporting

Sometimes it is necessary to override the default way of the step reporting. For example, if you want to use special characters that are not allowed in Java methods names, such as (, +, or %. You can then use the @As annotation to specify what JGiven should put into the report.

For example, if you define the following step method:

@As( "$ % are added" )
public WhenCalculator $_percent_are_added( int percent ) {
    return self();
}

The step will appear as 10 % are added in the report, when invoked with 10.

If you need full control over the step text you can inject the CurrentStep interface into the stage by using the @ScenarioState annotation. You can then use the setName method to change the steps text. Note, however, that this does not work well when used within parametrized scenarios, because JGiven will not be able anymore to generate data tables. In general, the preferred way to change the step name should be the @As annotation.

@ScenarioState
CurrentStep currentStep;

@ScenarioState
String internalData;

public MyStage some_step() {
    currentStep.setName( "some step " + internalData );
    return self();
}

5.3. Completely Hide Steps

Steps can be completely hidden from the report by using the @Hidden annotation. This is sometimes useful if you need a technical method call within a scenario, which should not appear in the report.

For example:

@Hidden
public void prepareRocketSimulator() {
    rocketSimulator = createRocketSimulator();
}

Note that it is useful to write hidden methods in CamelCase to make it immediately visible in the scenario that these methods will not appear in the report.

5.3.1. Method Parameters

The @Hidden annotation is also applicable for method parameters that shall not appear in the report.

For example:

public void setup_rocket(@Hidden String rocketName, @Hidden String rocketDescription) {
    this.rocketName = rocketName;
    this.rocketDescription = rocketDescription;
}

5.4. Extended Descriptions

Steps can get an extended description with the @ExtendedDescription annotation. You can use this to give additional information about the step to the reader of the report. In the HTML report this information is shown in a tooltip.

Example:

@ExtendedDescription("Actually uses a rocket simulator")
public RocketMethods launch_rocket() {
    rocketLaunched = rocketSimulator.launchRocket();
    return this;
}

5.5. Intro Words

If the predefined introductionary words in the Stage class are not enough for you and you want to define additional ones you can use the @IntroWord annotation on a step method.

Example:

@IntroWord
public SELF however() {
   return self();
}

Note that you can combine @IntroWord with the @As annotation. To define a , as an introductionary word, for example, you can define:

@IntroWord
@As(",")
public SELF comma() {
   return self();
}

5.6. Filler Words

Filler words can be used to build a common and reusable vocabulary for your tests. This enables you to write beautifully fluent tests whilst at the same time keeping your stage methods clear and concise.

The @FillerWord annotation can also be added to stage methods where you want to maintain the continuation of sentences in reports.

For example, given the following filler words:

@FillerWord
public SELF a() {
    return self();
}


@FillerWord
public SELF an() {
    return self();
}

@FillerWord
public SELF and() {
    return self();
}

@FillerWord
public SELF some() {
    return self();
}

@FillerWord
public SELF the() {
    return self();
}

You can write test stages such as:

given().the().ingredients()
    .an().egg()
    .some().milk()
    .and().the().ingredient( "flour" );

This would generate the following report:

Given the ingredients
      an egg
      some milk
      and the ingredient flour

Filler words can also be joined to other words in sentences. Using joinToPreviousWord and joinToNextWord you can create new words without trailing and leading whitespace respectively. A typical use case for using these attributes is adding punctuation to your tests.

For example, given the following filler words:

@As(",")
@FillerWord(joinToPreviousWord = true)
public SELF comma() {
    return self();
}

@As(":")
@FillerWord(joinToPreviousWord = true)
public SELF colon() {
    return self();
}

@As("(")
@FillerWord(joinToNextWord = true)
public SELF open_bracket() {
    return self();
}

@As(")")
@FillerWord(joinToPreviousWord = true)
public SELF close_bracket() {
    return self();
}

You could write test stages such as:

given().a().open_bracket().clean().close_bracket().worksurface().comma().a().bowl().and().the().ingredients().colon()
    .an().egg()
    .some().milk()
    .the().ingredient( "flour" );

This would generate the following report:

Given a (clean) worksurface, a bowl and the ingredients:
     an egg
     some milk
     the ingredient flour

Please note that the annotations @IntroWord and @FillerWord are mutually exclusive. @IntroWord takes precedence over @FillerWord; where there are occurrences of methods having both annotations @FillerWord will be ignored.

5.7. State Sharing

Very often it is necessary to share state between steps. As long as the steps are implemented in the same Stage class you can just use the fields of the Stage class. But what can you do if your steps are defined in different Stage classes? In this case you just define the same field in both Stage classes. Once in the Stage class that provides the value of the field and once in the Stage class that needs the value of the field. Both fields also have to be annotated with the special annotation @ScenarioState to tell JGiven that this field will be used for state sharing between stages. The values of these fields are shared between all stages that have the same field. For example, to be able to access the value of the ingredients field of the GivenIngredients stage in the WhenCook stage one has to annotate that field accordingly:

package com.tngtech.jgiven.examples.pancakes.test.steps;

import java.util.ArrayList;
import java.util.List;

import com.tngtech.jgiven.Stage;
import com.tngtech.jgiven.annotation.ProvidedScenarioState;

public class GivenIngredients extends Stage<GivenIngredients> {

    @ProvidedScenarioState
    List<String> ingredients = new ArrayList<String>();

    public GivenIngredients an_egg() {
        return the_ingredient( "egg" );
    }

    public GivenIngredients the_ingredient( String ingredient ) {
        ingredients.add( ingredient );
        return this;
    }

    public GivenIngredients some_milk() {
        return the_ingredient( "milk" );
    }

}
@JGivenStage
public class WhenCook extends Stage<WhenCook> {
    @Autowired
    @ScenarioState
    Cook cook;

    @ExpectedScenarioState
    List<String> ingredients;

    @ProvidedScenarioState
    Set<String> dough;

    @ProvidedScenarioState
    String meal;

    public WhenCook the_cook_fries_the_dough_in_a_pan() {
        assertThat( cook ).isNotNull();
        assertThat( dough ).isNotNull();

        meal = cook.fryDoughInAPan( dough );
        return this;
    }

Instead of the @ScenarioState annotation one can also use @ExpectedScenarioState and @ProvidedScenarioState to indicate whether the state is expected by the stage or provided by the stage. These function in exactly the same way as @ScenarioState but are more descriptive about what the code is doing.

5.7.1. Type vs. Name Resolution

Scenario state fields are by default resolved by its type. That is, you can only have one field of the same type as a scenario field. Exceptions are types from the packages java.lang.* and java.util.* which are resolved by the name of the field.

To change the resolution strategy you can use the resolution parameter of the @ScenarioState annotations. For example, to use name instead of type resolution you can write

@ScenarioState(resolution = Resolution.NAME).

5.7.2. Value Validation

By default, JGiven will not validate whether the value of a field of a stage that expects a value, was actually provided by a previous stage. The reason for this is that typically not all fields are always required for all steps. There might be scenarios where only a part of the fields are really necessary for the steps of the scenario. However, sometimes you know that a certain field value is needed for all steps of a stage. In this case you can set the required attribute of the @ScenarioState or @ExpectedScenarioState annotation to true. JGiven will then validate that a previous stage had provided the value and will throw an exception otherwise.

5.8. Having More Than 3 Stages

In many cases three stages are typically enough to write a scenario. However, sometimes more than three are required. JGiven provides two mechanism for that: stage injection and dynamic adding of stages.

5.8.1. Stage Injection

Additional stages can be injected into a test class by declaring a field with the additional stage and annotate it with @ScenarioStage.

Please note that a lifecycle model that reuses the same instance to execute multiple test methods is only partially compatible with JGiven. In particular, in such a model stages for different scenarios will be injected into the same field. This can lead to errors when attempting to run tests in parallel.

Example

In the following example we inject an additional stage GivenAdditionalState into the test class and use it in the test.

public class MyInjectedJGivenTest extends
   ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> {

   @ScenarioStage
   GivenAdditionalState additionalState;

   @Test
   public void something_should_happen() {
      given().some_state();

      additionalState
         .and().some_additional_state();

      when().some_action();
      then().some_outcome();
   }
}

Note that the field access will not be visible in the report. Thus the resulting report will look as follows:

Scenario: something should happen

Given some state
  And some additional state
 When some action
 Then some outcome

Also note that you should not forget to first invoke an intro method, like and() or given() on the injected stage before calling the step method.

5.8.2. Dynamic Addition of Stages

The disadvantage of injecting a stage into a test class is that this stage will be used for all tests of that class. This might result in an overhead if the stage contains @BeforeScenario or @AfterScenario methods, because these methods will also be executed in the injected stages. If an additional stage is only required for a single test method you should instead dynamically add that stage to the scenario by using the addStage method.

Example
import org.junit.Test;
import com.tngtech.jgiven.junit.ScenarioTest;

public class MyDynamicallyAddedTest extends
   ScenarioTest<GivenSomeState, WhenSomeAction, ThenSomeOutcome> {

   @Test
   public void something_should_happen() {
      GivenAdditionalState additionalState = addStage(GivenAdditionalState.class);

      given().some_state();

      additionalState
         .and().some_additional_state();

      when().some_action();
      then().some_outcome();
   }
}

5.9. Subclassing of Stages

In practice, it often makes sense to have a hierarchy of stage classes. Your top stage class can implement common steps that you require very often, while subclasses implement more specialized steps.

One problem with subclassing stage classes is to keep the fluent interface intact. Let’s have an example:

public class GivenCommonSteps extends Stage<GivenCommonSteps> {
    public GivenCommonSteps my_common_step() {
        return this;
    }

Now assume that we create a subclass of GivenCommonSteps:

public class GivenSpecialSteps extends GivenCommonSteps {
    public GivenSpecialSteps my_special_step() {
        return this;
    }
}

If you now want to use the GivenSpecialSteps stage, you will get problems when you want to chain multiple step methods:

@Test
public void subclassing_of_stages_should_work() {
    given().my_common_step()
      .and().cant_do_this();
}

This code will not compile, because my_common_step() returns GivenCommonSteps and not GivenSpecialSteps.

Luckily this problem can be fixed with generic types. First you have to change the GivenCommonSteps class as follows:

public class GivenCommonStepsFixed<SELF extends GivenCommonStepsFixed<SELF>> extends Stage<SELF> {
    public SELF my_common_step() {
        return self();
    }
}

That is, you give GivenCommonSteps a type parameter SELF that can be specialized by subclasses. This type is also used as return type for my_common_step(). Instead of returning this you return self(), which is implemented in the Stage class.

Now your subclass must be change as well:

public class GivenSpecialStepsFixed<SELF extends GivenSpecialStepsFixed<SELF>>
        extends GivenCommonStepsFixed<SELF> {
    public SELF my_special_step() {
        return self();
    }
}

6. Life-Cycle Methods

6.1. Scenario Life-Cycle

The life-cycle of a scenario is as follows.

  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:

    1. Values are injected into all scenario state fields

    2. The @BeforeStage-annotated methods of the stage are called

    3. The steps of the stage are executed

    4. The @AfterStage-annotated methods of the stage are called

    5. 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.

6.1.1. Integration into Test Frameworks' Lifecycles

The relative ordering of JGiven’s lifecycle methods to that of the embedding test framework depends on how JGiven is hooked into it. For JUnit4, JGiven’s before and after methods are called later than their respective framework counterparts. Especially the @After method is executed ahead of an @AfterScenario method or an after() method of a scenario rule. For JUnit5 and TestNg, JGiven’s before() method is executed later than the before method and JGiven’s after() method is executed ahead of the frameworks' counterparts.

6.2. @BeforeScenario / @AfterScenario

6.2.1. @BeforeScenario

Methods annotated with @BeforeScenario are executed before any step of any stage of a scenario. This is useful for some general scenario setup. As an example, you could initialize a WebDriver instance for Selenium tests or set up a database connection. Note that such methods are executed before the injection of values. Thus, the annotated code must not depend on scenario state fields.

@ProvidedScenarioState
protected WebDriver webDriver;

@BeforeScenario
public void startBrowser() {
    webDriver = new HtmlUnitDriver( true );
}

6.2.2. @AfterScenario

Analogous to @BeforeScenario there is the @AfterScenario annotation to execute a methods when a scenario has finished.

@ProvidedScenarioState
protected WebDriver webdriver;

@AfterScenario
public void closeBrowser() {
    webdriver.close();
}

6.3. @ScenarioRule

@BeforeScenario and @AfterScenario methods are often used to set up and tear down some resource. If you want to reuse such a resource in another stage, you would have to copy these methods into that stage. To avoid that, JGiven offers a concept similar to JUnit rules. The idea is to have a separate class that only contains the before and after methods. This class can then be reused in multiple stages.

A scenario rule class can be any class that provides the methods before() and after(). This is compatible with JUnit’s ExternalResource class, which means that you can use classes like TemporaryFolder from JUnit directly in JGiven.

6.3.1. Example

public class WebDriverRule {

    protected WebDriver webDriver;

    public void before() {
         webDriver = new HtmlUnitDriver( true );
    }

    public void after() {
         webDriver.close();
    }
 }


public class MyWebDriverUsingStage {
    @ScenarioRule
    protected WebDriverRule webDriverRule = new WebDriverRule();
}

6.4. @BeforeStage / @AfterStage

6.4.1. @BeforeStage

Methods annotated with @BeforeStage are executed after injection, but before the first step method is called. This is useful for setting up code that is required by all or most steps of a stage.

@BeforeStage
public void setup() {
  // do something useful to set up the stage
}

6.4.2. @AfterStage

Analogous to @BeforeStage methods, methods can be annotated with @AfterStage. These methods are executed after all steps of a stage have been executed, but before extracting the values of scenario state fields. Thus, you can prepare state for following stages in an @AfterStage method. A typical use case for this is to apply the builder pattern to build a certain object using step methods and build the final object in an @AfterStage method.

6.4.3. Example

public class MyStage {

    protected CustomerBuilder customerBuilder;

    @ProvidedScenarioState
    protected Customer customer;

    public MyStage a_customer() {
        customerBuilder = new CustomerBuilder();
        return this;
    }

    public MyStage the_customer_has_name( String name ) {
        customerBuilder.withName( name );
        return this;
    }

    @AfterStage
    public void buildCustomer() {
        if (customerBuilder != null) {
            customer = customerBuilder.build();
        }
    }
 }

6.4.4. Repeatable Stage Methods

The @BeforeStage and @AfterStage methods come with an optional parameter that allows to execute these methods each time you enter or leave the stage, respectively.

@BeforeStage(repeatable=true)
void setup(){
    //do something useful several times
}

The process of entering or leaving a stage is determined by a change of the active stage class. Hence, in order for a repeated execution to work, a different stage has had to be executed in between two invocations of the same stage. Therefore, the following setup only executes the @BeforeStage once.

class RepeatableStage extends Stage<RepeatableStage>{
    @BeforeStage(repeatable=true)
    void beforeStageMethod(){ /*...*/}

    public void given_method(){/*...*/}

    public void when_method() {/*...*/}
}
class UseRepeatableStage {
    //...
    @Test
    public void test(){
        //@BeforeStage will only be executed before this invocation
        repeatableStage.given().given_method();
        //Still the same stage class, no @BeforeStage execution here
        repeatableStage.when().when_method();
    }
}

Likewise, @AfterStage methods in such scenarios will only be executed after the last invocation of the stage.

7. Parameterized Steps

Step methods can have parameters. Parameters are formatted in reports by using the String.valueOf method, applied to the arguments. The formatted arguments are added to the end of the step description.

given().the_ingredient( "flour" ); // Given the ingredient flour
given().multiple_arguments( 5, 6 ); // Given multiple arguments 5 6

7.1. Parameters within a sentence

To place parameters within a sentence instead of the end of the sentence you can use the $ character.

given().$_eggs( 5 );

In the generated report $ is replaced with the corresponding formatted parameter. So the generated report will look as follows:

Given 5 eggs

If there are more parameters than $ characters, the remaining parameters are added to the end of the sentence.

If a $ should not be treated as a placeholder for a parameter, but printed verbatim, you can write $$, which will appear as a single $ in the report.

7.2. Custom Annotations

The @As annotation can be used to override the shown sentence. To reference arguments you can use the three different interoperable options which apply to every description in JGiven. For more examples look up JGiven StepFormatter Tests

Use $ to access the arguments in natural order.

@As ( "the $ fresh eggs and the $ cooked rice bowls" )
public SELF $_eggs_and_$_rice_bowls( int eggs, int riceBowls ) { ... }

Or enumerate them $1, $2, …​ to have a direct reference:

@As ( "the $1 fresh eggs and the $2 cooked rice bowls" )
public SELF $_eggs_and_$_rice_bowls( int eggs, int riceBowls ) { ... }

Or reference them via the argument names:

@As ( "the $eggs fresh eggs and the $riceBowls cooked rice bowls" )
public SELF $_eggs_and_$_rice_bowls( int eggs, int riceBowls ) { ... }

The call to given().$_eggs_and_$_rice_bowls(5, 2) will be shown as:

Given the 2 cooked rice bowls and the 5 fresh eggs

7.3. Extended Description

An extended description is shown if you hover above the step as tooltip, but hidden by default.

@ExtendedDescription ( "The $2 rice bowls were very delicious" )
public SELF $_eggs_and_$_rice_bowls( int eggs, int riceBowls ) { ... }

The call to given().$_eggs_and_$_rice_bowls(5, 2) will still be shown as:

Given 5 eggs and 2 rice bowls

The extended description can also be set by using the CurrentStep interface, which can be injected into a state with the @ScenarioState annotation.

@ScenarioState
CurrentStep currentStep;

public SELF $_eggs_and_$_rice_bowls( int eggs, int riceBowls ) {
    currentStep.setExtendedDescription( "The " + riceBowls + " rice bowls were very delicious" );
}

7.4. Parameter Formatting

Sometimes the toString() representation of a parameter object does not fit well into the report. In these cases you have three possibilities:

  1. Change the toString() implementation. This is often not possible or not desired, because it requires the modification of production code. However, sometimes this is appropriate.

  2. Provide a wrapper class for the parameter object that provides a different toString() method. This is useful for parameter objects that you use very often.

  3. Change the formatting of the parameter by using special JGiven annotations. This can be used in all other cases and also to change the formatting of primitive types.

7.5. The @Format annotation

The default formatting of a parameter can be overridden by using the @Format annotation. It takes as a parameter a class that implements the ArgumentFormatter interface. In addition, an optional array of arguments can be given to configure the customer formatter. For example, the built-in BooleanFormatter can be used to format boolean values:

public SELF the_machine_is_$(
    @Format( value = BooleanFormatter.class, args = { "on", "off" } ) boolean onOrOff ) {
    ...
}

In this case, true values will be formatted as on and false as off.

7.6. Custom formatting annotations

As using the @Format annotation is often cumbersome, especially if the same formatter is used in multiple places, one can define and use custom formatting annotations instead.

An example is the pre-defined @Quoted annotation, which surrounds parameters with quotation marks. The annotation is defined as follows:

@Format( value = PrintfFormatter.class, args = "\"%s\"" )
@Retention( RetentionPolicy.RUNTIME )
@Target( { ElementType.PARAMETER, ElementType.ANNOTATION_TYPE } )
public @interface Quoted {}

As you can see, the annotation itself is annotated with the @Format annotation as described above, which will be applied to all parameters that are annotated with @Quoted.

7.6.1. Example

public SELF the_message_$_is_printed_to_the_console( @Quoted message ) { ... }

When invoked as

then().the_message_$_is_printed_to_the_console( "Hello World" );

Then this will result in the report as:

Then the message "Hello World" is printed to the console

7.6.2. The @AnnotationFormat annotation

Another pre-defined annotation is the @Formatf annotation which uses the @AnnotationFormat annotation to specify the formatter. Formatters of this kind implement the AnnotationArgumentFormatter interface. This allows for very flexible formatters that can take the concrete arguments of the annotation into account.

@AnnotationFormat( value = PrintfAnnotationFormatter.class )
@Retention( RetentionPolicy.RUNTIME )
@Target( ElementType.PARAMETER )
public @interface Formatf {
    String value() default "%s";
}

7.7. The @POJOFormat annotation

When step parameter is a POJO, it may sometimes be useful to get a string representation using part or all of the fields composing this POJO.

The @POJOFormat fulfills this need while providing a way to format fields values following customizable field formats.

Given the following POJO :

class CoffeeWithPrice {
   String name;
   double price_in_EUR;
   CoffeeWithPrice(String name, double priceInEur) {
      this.name = name;
      this.price_in_EUR = priceInEur;
   }
}

Then you can define a step method as follows:

public SELF the_coffee_price_$_is_registered( @POJOFormat(fieldFormats = {
                    @NamedFormat( name = "name", customFormatAnnotation = Quoted.class),
                    @NamedFormat( name = "price_in_EUR", format = @Format( value = PrintfFormatter.class, args = "%s EUR" ) )
                } ) CoffeeWithPrice price ) {
  ...
}

where @NamedFormat associates a format (classic @Format or any custom format annotation) to a field by its name.

Finally, the step method can be called with an argument :

given().the_coffee_price_$_is_registered(new CoffeeWithPrice("Espresso", 2.0));

Then the report will look as follows:

Given the coffee price ["Espresso",2.0 EUR] is registered

For additional options, see the JavaDoc documentation of the @POJOFormat annotation

7.7.1. Reuse a set of @NamedFormat definitions

When several steps use the same type of POJO in their parameters, it may be tedious to redefine this POJO fields format in each of these steps.

The solution in this case is to create a custom annotation where POJO fields formats will be declared once and for all.
This custom annotation will be itself annotated with the @NamedFormats which will wrap as much @NamedFormat as there are fields needing a specific formatting.
It can then further be referenced by any @POJOFormat and @Table annotations through their respective fieldsFormatSetAnnotation attribute.

Given the following POJO :

class CoffeeWithPrice {
   String name;
   double price_in_EUR;
   CoffeeWithPrice(String name, double priceInEur) {
      this.name = name;
      this.price_in_EUR = priceInEur;
   }
}

Then you can specify a reusable set of formats for each field of this POJO through a new custom annotation :

@NamedFormats( {
   @NamedFormat( name = "name", customFormatAnnotation = Quoted.class),
   @NamedFormat( name = "price_in_EUR", format = @Format( value = PrintfFormatter.class, args = "%s EUR" ) )
} )
@Retention( RetentionPolicy.RUNTIME )
public @interface CoffeeWithPriceFieldsFormatSet {}

Then you will be able to reuse this custom named formats set annotation into the kind of steps below :

public SELF the_coffee_price_$_is_registered( @POJOFormat(fieldsFormatSetAnnotation = CoffeeWithPriceFieldsFormatSet.class ) CoffeeWithPrice price ) {
  ...
}
public SELF expected_coffee_price_for_name_$_is_$(@Quoted String coffeeName, @POJOFormat(fieldsFormatSetAnnotation = CoffeeWithPriceFieldsFormatSet.class ) CoffeeWithPrice price ) {
  ...
}

7.7.2. Field-level format definition

If you have full control over the POJO class, you can also specify fields format directly into the POJO class, at field level, by annotating POJO fields with any format (or chain of formats) of your choice.
JGiven will then make use of field-level format annotations within a @POJOFormat or @Table context of use.

Given the following POJO with field-level specified formats :

class CoffeeWithPrice {

   @Quoted
   String name;

   @Format( value = PrintfFormatter.class, args = "%s EUR" )
   double price_in_EUR;

   CoffeeWithPrice(String name, double priceInEur) {
      this.name = name;
      this.price_in_EUR = priceInEur;
   }
}

Then you can define a step method as follows:

public SELF the_coffee_price_$_is_registered(@POJOFormat CoffeeWithPrice price ) {
  ...
}

Finally, the step method can be called with an argument :

given().the_coffee_price_$_is_registered(new CoffeeWithPrice("Espresso", 2.0));

Then the report will look as follows:

Given the coffee price ["Espresso",2.0 EUR] is registered

Please note that @NamedFormat specified at @POJOformat or @Table level have precedence over field-level defined formats.

7.8. Tables as Parameters

Sometimes information can be represented very concisely by using tables. JGiven supports this with the @Table annotation for step parameters. Such parameters are then formatted as tables in the report. The types of such parameters can be:

  1. A list of lists, where each inner list represents a single row and the first row represents the headers of the table.

  2. A list of POJOs, where each POJO represents a row and the headers are inferred by the names of the fields of the POJO.

  3. A single POJO, which is equivalent to a one-element list of POJOs.

7.8.1. Example

Given the following POJO:

class CoffeeWithPrice {
   String name;
   double price_in_EUR;
   CoffeeWithPrice(String name, double priceInEur) {
      this.name = name;
      this.price_in_EUR = priceInEur;
   }
}

Then you can define a step method as follows:

public SELF the_prices_of_the_coffees_are( @Table CoffeeWithPrice... prices ) {
  ...
}

Finally, the step method can be called with a list of arguments:

given().the_prices_of_the_coffees_are(
   new CoffeeWithPrice("Espresso", 2.0),
   new CoffeeWithPrice("Cappuccino", 2.5));

Then the report will look as follows:

Given the prices of the coffees are

| name       | price in EUR |
+------------+--------------+
| Espresso   | 2.0          |
| Cappuccino | 2.5          |

For additional options, see the JavaDoc documentation of the @Table annotation

Also note that POJO fields formats can be specified thanks to the @Table#fieldsFormat or @Table#fieldsFormatSetAnnotation options.
See The @POJOFormat annotation section for more information about how to use these two options.

8. Parameterized Scenarios

JGiven scenarios can be parameterized. This is very useful for writing data-driven scenarios, where the scenarios itself are the same, but are executed with different example values.

Parameterization of Scenarios works with TestNG and JUnit, we only show it for JUnit. For TestNG it works analogous.

8.1. JUnit

JGiven supports several different ways to parameterize a JUnit test:

  1. JUnit’s built-in Parametrized Runner

  2. JUnit DataProvider

  3. JUnitParams

We use the JUnit Dataprovider in the following.

8.2. JUnit Dataprovider Runner

JUnit Dataprovider provides a JUnit test runner that enables the execution of paramterized test methods. It is similar to the way parameterized tests work in TestNG.

8.2.1. Example

    @Test
    @DataProvider( {
            "1, 1",
            "0, 2",
            "1, 0"
    } )
    public void coffee_is_not_served( int coffees, int euros ) {
        given().a_coffee_machine()
                .and().the_coffee_costs_$_euros( 2 )
                .and().there_are_$_coffees_left_in_the_machine( coffees );

        when().I_insert_$_one_euro_coins( euros )
                .and().I_press_the_coffee_button();

        then().I_should_not_be_served_a_coffee();
    }

The resulting report will then look as follows:

 Coffee is not served

   Given a coffee machine
     And the coffee costs 2 euros
     And there are <coffees> coffees left in the machine
    When I insert <euros> one euro coins
     And I press the coffee button
    Then I should not be served a coffee

  Cases:

   | # | coffees | euros | Status  |
   +---+---------+-------+---------+
   | 1 |       1 |     1 | Success |
   | 2 |       0 |     2 | Success |
   | 3 |       1 |     0 | Success |

8.3. Case As Description

If your test has multiple cases, you can use the @CaseAs to provide additional information to the description for each case.

    @Test
    @DataProvider( {
            "On the first run, 1, quite ok",
            "And on the second run, 2, well-done"
    } )
    @CaseAs( "$1" )
    public void coffee_making_gets_better( String description, int runNr, String result ) {
        given().a_coffee_machine();
        when().I_make_coffee_for_the_$_time( runNr );
        then().the_result_is( result );
    }

The resulting report will then look as follows:

 Coffee making gets better

   Given a coffee machine
    When I make coffee for the <runNr> time
    Then the result is <result>

  Cases:

   | # | Description           | runNr | result    | Status  |
   +---+-----------------------+-------+-----------+---------+
   | 1 | On the first run      |     1 | quite ok  | Success |
   | 2 | And on the second run |     2 | well-done | Success |

9. Tags

Tags are used to organize scenarios. A tag in JGiven is just a Java annotation that is itself annotated with the @IsTag annotation. You can annotate whole test classes or single test methods with tag annotations. Tags then appear in the resulting report.

Let’s say you want to know which scenarios cover the coffee feature. To do so you define a new Java annotation:

@IsTag
@Retention( RetentionPolicy.RUNTIME )
public @interface ExampleCategory {}

Two things are important:

  1. The annotation itself must be annotated with the @IsTag annotation to mark it as a JGiven tag.

  2. The annotation must have retention policy RUNTIME so that JGiven can recognize it at runtime.

To tag a scenario with the new tag, you just annotate the corresponding test method:

@ExampleSubCategory
@Test
public void tags_can_form_a_hierarchy() {
    given().tags_annotated_with_tags();
    when().the_report_is_generated();
    then().the_tags_appear_in_a_hierarchy();
}

In the report the scenario will then be tagged with tag CoffeeFeature.

You can also annotate the whole test class, in which case all scenarios of that class will get the tag.

9.1. Descriptions

Tags can have descriptions. These descriptions appear in the report on the corresponding page for the tag. For example:

@IsTag( style = "background-color: darkgreen; color: white; font-weight: bold",
    description = "Tags can be arbitrarily styled with the 'style' attribute of the '@IsTag' annotation. " +
            "This tag shows how to apply such a custom style" )
@Retention( RetentionPolicy.RUNTIME )
public @interface TagsWithCustomStyle {}

9.2. Overriding the Name

It is possible to override the name of a tag by using the name attribute of the IsTag annotation. This allows you to have a different name for the tag than the actual type of the annotation. For example, if you want to have a tag Feature: Coffee you can define the CoffeeFeature annotation as follows:

@IsTag( name = "Feature: Coffee" )
@Retention( RetentionPolicy.RUNTIME )
public @interface CoffeeFeature { }

9.3. Values

Sometimes you do not want to always create a new annotation for each tag. Let’s say you organize your work in stories and for each story you want to know which scenarios have been written for that story. Instead of having a separate annotation for each story you can define a Story annotation with a value() method:

@IsTag( prependType = false )
@Retention( RetentionPolicy.RUNTIME )
public @interface CategoryWithValue {
    String value();
}

Annotations with different values are treated as different tags in JGiven. So using the above annotation you can now mark scenarios to belong to certain stories:

@Test @Story("ACME-123")
public void scenarios_can_have_tags() {
  ...
}

In the report the tag will now be ACME-123 instead of Story.

If you want that the type is prepended to the value in the report you can set the prependType attribute of the IsTag annotation to true. In this case the tag will be shown as Story-ACME-123. Note that this feature works in combination with the name attribute.

Annotations with the same type but different values are grouped in the report. E.g. multiple @Story tags with different values will be grouped under Story.

9.3.1. Array Values

The value of a tag annotation can also be an array:

@IsTag
@Retention( RetentionPolicy.RUNTIME )
public @interface Story {
    String[] value();
}

This allows you to give the same scenario multiple tags of the same type with different values:

@Test @Story( {"ACME-123", "ACME-456"} )
public void scenarios_can_have_tags() {
  ...
}

For each value, one tag will be generated, e.g. ACME-123 and ACME-456. If you do not want that behavior you can set the explodeArray attribute of @IsTag to false, in that case only one tag will be generated and the values will comma-separated, e.g. ACME-123,ACME-456.

9.3.2. Value Dependent Description

When the description of a tag depends on its value you cannot simply set the description on the @IsTag annotation, because it will be the same for all values.

Let’s assume you have an @Issue tag and want to have a link to the corresponding GitHub issue in the description. To do so you can provide your own TagDescriptionGenerator implementation that generates a description of a tag depending on its actual value:

public class IssueDescriptionGenerator implements TagDescriptionGenerator {
    @Override
    public String generateDescription( TagConfiguration tagConfiguration,
            Annotation annotation, Object value ) {
        return String.format(
           "<a href='https://github.com/TNG/JGiven/issues/%s'>Issue %s</a>",
            value, value );
    }
}

The new IssueDescriptionGenerator must now be configured for the @Issue annotation using the descriptionGenerator attribute of @IsTag:

@IsTag( descriptionGenerator = IssueDescriptionGenerator.class )
@Retention( RetentionPolicy.RUNTIME )
public @interface Issue {
    String[] value();
}

9.4. Overriding the Value

If you want to group several annotations with different types under a common name. You can combine the name attribute with the value attribute as follows:

@IsTag( name = "Feature", value = "Tag" )
@Retention( RetentionPolicy.RUNTIME )
public @interface FeatureTags { }

The tag will appear as Tag in the report and all annotations with name Feature will be grouped together.

9.5. Hierarchical Tags

If your number of tags grow you typically want to organize your tags somehow. This is easily possible in JGiven by forming tag hierarchies. A tag hierarchy is defined by just annotating a tag annotation with other tags. Each of these tags will then become a parent tag in the hierarchy. For example, if you want to organize your features into ‘Core Features’ and ‘Secondary Features’ you can do so by first defining two tags @CoreFeature and @SecondaryFeature for each of these categories as you would define a normal JGiven tag. If you now want to define a feature tag as a Core Feature you just annotated that tag accordingly:

@CoreFeature
@IsTag
@Retention( RetentionPolicy.RUNTIME )
public @interface OneOfMyCoolCoreFeatures {}

10. Attachments

Steps can have attachments. This is useful, for example, to save screenshots during tests or to refer to larger files that should not be printed inline in the report.

10.1. Creating Attachments

There are several ways to create an attachment by using static factory methods of the Attachment class. For example, you can create textual attachments from a given String by using the fromText method.

Attachment attachment = Attachment.fromText("Hello World", MediaType.PLAIN_TEXT);

10.1.1. Titles

Attachments can have an optional title which can be used by reports, for example, to show a tooltip. The title is set with the withTitle method:

Attachment attachment = Attachment
    .fromText("Hello World", MediaType.PLAIN_TEXT)
    .withTitle("Some Title");

10.1.2. Binary Attachments

Binary attachments are internally stored by JGiven as Base64-encoded strings. If the binary content is already present as a Base64-encoded string one can just use that to create a binary attachment:

Attachment attachment = Attachment
    .fromBase64("SGVsbG8gV29ybGQK", MediaType.application("jgiven"));

10.2. Adding Attachments to Steps

To add an attachment to a step you have to first inject the CurrentStep class into your stage class by using @ExpectedScenarioState.

@ExpectedScenarioState
CurrentStep currentStep;

Now you can use currentStep inside step methods to add attachments using the addAttachment method. The method takes as argument an instance of Attachment.

public SELF my_step_with_attachment() {
    Attachment attachment = ...
    currentStep.addAttachment( attachment );
    return self();
}

10.3. Example: Taking Screenshots with Selenium WebDriver

If you are using Selenium WebDriver and want to add screenshots to a JGiven step you can do so as follows:

String base64 = ( (TakesScreenshot) webDriver ).getScreenshotAs( OutputType.BASE64 );
currentStep.addAttachment( Attachment.fromBase64( base64, MediaType.PNG )
           .withTitle( "Screenshot" ) );

For a full example, see the Html5AppStage class that is used by the JGiven tests.

11. Exception Handling

When writing JGiven scenarios you should know how JGiven handles exceptions. In general, JGiven captures all exceptions that are thrown in step methods. This is done, so that the steps following the erroneous step can still be executed by JGiven to show them in the report as skipped steps. After the whole scenario has been executed, JGiven will rethrow the exception that has been previously thrown, so that the overall test actually fails. This behavior is in general no problem as long as you do not really expect exceptions to happen.

When using TestNG as a test runner, JGiven does not catch exceptions. The reason is that rethrowing the exception at the end of the scenario is not working well together with the TestNG execution model.

11.1. Expecting Exceptions

Let’s assume you want to verify that in the step method doing_some_action a certain exception is thrown and you write the following scenario using JUnit:

// DOES NOT WORK!
@Test(expected = MyExpectedException.class)
public void an_expected_exception_should_be_thrown() {
    given().some_erroneous_precondition();
    when().doing_some_action();
}

This scenario has two drawbacks: first it will not work and second the generated report will not make clear that actually an exception is expected. It will not work, because the JUnit mechanism to check for an expected exception actually comes too early. As already explained above, JGiven will throw the exception itself, after the scenario has finished. As JGiven is actually implemented as a JUnit rule, throwing this exception will be after JUnit has checked for an expected exception. This technical issue can be fixed by using the ExpectedException rule of JUnit:

@Rule
public ExpectedException rule = ExpectedException.none();

@Test
public void an_expected_exception_should_be_thrown() {
   // will work, but is not visible in the report
   rule.expect(MyExpectedException.class);
   given().some_erroneous_precondition();
   when().doing_some_action();
}

In this case, the actual verification of the exception is done after JGiven has thrown the exception and thus the ExpectedException rule will get the exception.

11.2. A better approach

The above example has still the big disadvantage that it will not be visible in the report that an exception is actually expected. More helpful would be the following scenario:

@Test
public void an_expected_exception_should_be_thrown() {
   given().some_erroneous_precondition();
   when().doing_some_action();
   then().an_exception_is_thrown();
}

Note, that this is scenario is still very technical and you should consider replacing the word ‘exception’ with a more domain specific term.

In order to realize the above scenario you have to explicitly catch the exception in the doing_some_action step and store it into a scenario state field.

@ProvidedScenarioState
MyExpectedException someExpectedException;

public SELF doing_some_action() {
   try {
       ...
   } catch (MyExpectedException e) {
      someExpectedException = e;
   }
   return self();
}

In the When-Stage you then just have to check whether the field is set:

@ExpectedScenarioState
MyExpectedException someExpectedException;

public SELF an_exception_is_thrown() {
    assertThat( someExpectedException ).isNotNull();
    return self();
}

12. Spock

Spock support is currently in an experimental state. This means that the API is not completely stable yet and that not all JGiven and Spock features might be supported yet.

12.1. Install Dependency

Spock support is provided by the jgiven-spock dependency.

12.1.1. Maven

<dependency>
   <groupId>com.tngtech.jgiven</groupId>
   <artifactId>jgiven-spock</artifactId>
   <version>1.3.1</version>
   <scope>test</scope>
</dependency>

12.1.2. Gradle

dependencies {
    testImplementation "com.tngtech.jgiven:jgiven-spock:1.3.1"
}

12.2. Use JGiven with Spock

JGiven support for Spock is provided by extending ScenarioSpec<Given, When, Then> and JGiven is enabled.

class SpockSpec extends ScenarioSpec<Given, When, Then> {

   def "my scenario"() {
      expect:

      given().some_context()
      when().some_action()
      then().some_outcome()
   }
}

12.3. Example Project

You find a complete example project on GitHub: https://github.com/TNG/JGiven/tree/master/example-projects/spock

13. Spring

Only two steps are required to use JGiven together with Spring or Spring-Boot. First, install the right dependency. Second, make your Spring Test Configuration aware of JGiven.

If you’re using JUnit, however, you need to inherit from a spring scenario class. Finally, if you want your test stages to act as Spring Beans, you need to add a special annotation to them.

13.1. Install Dependency

Spring support is provided by the jgiven-spring-junit4 and the jgiven-spring-junit5 dependencies, depending on which JUnit version you are using. JUnit 5 is only supported with version 5 of Spring.

13.1.1. Maven

<dependency>
   <groupId>com.tngtech.jgiven</groupId>
   <artifactId>jgiven-spring-junit5</artifactId>
   <version>1.3.1</version>
   <scope>test</scope>
</dependency>

13.1.2. Gradle

dependencies {
    testImplementation("com.tngtech.jgiven:jgiven-spring-junit5:1.3.1")
}

13.2. Configure Spring

In order to enable the JGiven Spring integration you have to tell Spring about the existence of JGiven.

13.2.1. Annotation-Based

If you are using the annotation-based configuration of Spring you can annotate your Spring Test Configuration with the @EnableJGiven annotation. This is all you have to do to configure Spring for JGiven.

Example
@EnableJGiven
@Configuration
@ComponentScan("the project")
public class MySpringTestConfig {
   //...
}

13.2.2. XML-Based

You can also configure JGiven with XML by adding the jgiven:annotation-driven tag to your Spring XML config.

Example
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jgiven="http://jgiven.org/jgiven-spring"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
                        http://jgiven.org/jgiven-spring http://jgiven.org/schemas/jgiven-spring.xsd">

  <jgiven:annotation-driven/>

</beans>

13.3. Use JGiven in JUnit-based Spring Tests

13.3.1. JUnit 4

In order to use JGiven in JUnit 4 based Spring tests, you should inherit from:

  • SpringRuleScenarioTest

  • SimpleSpringRuleScenarioTest

These base classes contain all the methods and JUnit rules necessary to run JGiven with Spring.

13.3.2. JUnit 5

To use JGiven in JUnit 5 based Spring Tests you should inherit from one of the provided tests classes:

  • SpringScenarioTest

  • SimpleSpringScenarioTest

Both classes contain all the annotations and methods necessary to run JGiven with Spring. The SimpleSpringScenarioTest allows to have all stages in a single stage class, while the SpringScenarioTest allows to have a class for each stage.

Example
@SpringBootTest(classes={MySpringTestConfig.class})
class MySpringBootApplicationTest extends SimpleSpringScenarioTest<MyStage> {
   //...
}

If you cannot or don’t want to inherit from these classes, you can copy the content of these classes (one method) into one of your test base classes.

13.4. Stages as Spring Beans

In order to treat JGiven stages as Spring beans, e.g. if you want to inject other Spring beans into your stages, you have to annotate the stage class with the @JGivenStage annotation.

13.4.1. Example

@JGivenStage
public class MyStage {

   @Autowired
   MyService myService;
}

13.5. Example Project

You find a complete example project, a SpringBoot webapp tested with JGiven, on GitHub: https://github.com/TNG/JGiven/tree/master/example-projects/spring-boot

14. Web Testing with Selenium

JGiven is a completely use-case agnostic testing library. So it is no surprise that you can also write Selenium-Tests with JGiven. In fact, JGiven can be considered as a perfect addition on top of Selenium: In Selenium you define the low-level interaction with the browser and HTML pages. In JGiven you define a high-level, domain specific language to specify your scenarios.

There is no additional integration module needed in order to use JGiven with Selenium

14.1. Page Objects vs Stages

It is possible to treat Selenium page objects directly as JGiven stages. This sometimes makes sense for simple cases, but from a code design perspective, it is better to separate these different concerns into multiple classes. The page object should be solely responsible for the low-level interaction with a HTML page. The stage class contains the high-level steps of the scenario and handles the state of the scenario.

14.2. Example Project

You find a complete example project on GitHub: https://github.com/TNG/JGiven/tree/master/example-projects/selenium

15. Android

JGiven can be used to test Android applications. For unit-tests, which are not running on an Android device or emulator, you can just use jgiven-junit. You don’t have to do any special test setup to get that working.

15.1. Testing on an Device or Emulator (EXPERIMENTAL)

If you want to execute your tests on an Android device or emulator, for example when you want to use the Espresso test framework, then you need some additional setup.

This feature exists since version 0.14.0-RC1 and is still in an experimental status. This means that it is not tested very well yet and that the implementation might be changed in a backwards-incompatible way in future versions of JGiven.

15.1.1. JGiven Android Dependency

First of all you need an additional project dependency to the jgiven-android module:

dependencies {
    androidTestCompile("com.tngtech.jgiven:jgiven-android:1.3.1")
}

15.1.2. Enable additional Permissions

JGiven writes JSON files during the execution of the tests to the external storage of the device or emulator. Thus you have to enable the permission WRITE_EXTERNAL_STORAGE when executing the tests. You can do that by creating a file src/debug/AndroidManifest.xml that enables that permission:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="the.package.of.my.app">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

</manifest>

15.1.3. AndroidJGivenTestRule

In your test classes you have to add the following additional JUnit rule:

@Rule
public AndroidJGivenTestRule androidJGivenTestRule = new AndroidJGivenTestRule(this.getScenario());

In addition, it is useful to expose the ActivityTestRule to Stages by using the @ScenarioState annotation:

@Rule
@ScenarioState
public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class);

15.1.4. Pull JGiven JSON files from device

When the test execution has finished, you need to pull the JGiven JSON files from the device to the local drive. You can do that using the adb tool. The following Gradle task does the job:

def targetDir = 'build/reports/jgiven/json'
def adb = android.getAdbExe().toString()
def reportsDir = '/storage/emulated/0/Download/jgiven-reports'

task cleanJGivenReports(type: Delete) {
  delete targetDir
}

task pullJGivenReports(type: Exec, dependsOn: cleanJGivenReports) {
    doFirst {
        if (!file(targetDir).mkdirs()) {
            println("Cannot create dir "+targetDir)
        }
    }

    commandLine adb, 'pull', reportsDir, targetDir

    doLast {
        println("Pulled "+reportsDir+" to "+targetDir);
    }
}

15.1.5. Generate HTML Report

Given the local JSON files, you can generate the HTML report as described in Report Generation

15.1.6. Taking Screen Shots in Espresso Tests

It is very useful to take screen shots during your tests and add them to the JGiven documentation. When using the Espresso framework, you can use the following piece of code to take a screenshot of an activity and turn it into a byte array:

public class ScreenshotUtil {
    public static byte[] takeScreenshot(Activity activity) {
        View view = activity.getWindow().getDecorView().getRootView();
        view.setDrawingCacheEnabled(true);
        Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
        view.setDrawingCacheEnabled(false);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
        return baos.toByteArray();
    }
}

This byte array can then be added to a step of a JGiven report as usual:

public class MyStage {
    @ScenarioState
    CurrentStep currentStep;

    @ScenarioState
    ActivityTestRule<MainActivity> activityTestRule;

    public Steps clicking_the_Click_Me_button() {
        onView(withId(R.id.clickMeButton)).perform(click());
        takeScreenshot();
        return this;
    }

    @Hidden
    public void takeScreenshot() {
        currentStep.addAttachment(
            Attachment.fromBinaryBytes(
                ScreenshotUtil.takeScreenshot( activityTestRule.getActivity()),
                MediaType.PNG).showDirectly()) ;
    }
}

15.2. Example Project

You find a complete example project on GitHub: https://github.com/TNG/JGiven/tree/master/example-projects/android

16. JUnit 5

16.1. Known Issue

  • Dynamic tests are not reported at all. As dynamic tests in JUnit 5 do not provide life-cycle hooks, it is unclear at the moment, whether JGiven will ever support them.

16.2. Install Dependency

JUnit 5 support is provided by the jgiven-junit5 dependency.

16.2.1. Maven

<dependency>
   <groupId>com.tngtech.jgiven</groupId>
   <artifactId>jgiven-junit5</artifactId>
   <version>1.3.1</version>
   <scope>test</scope>
</dependency>

16.2.2. Gradle

dependencies {
    testImplementation("com.tngtech.jgiven:jgiven-junit5:1.3.1")
}

16.3. Use JGiven with JUnit 5

JGiven support for JUnit 5 is provided by the JGivenExtension JUnit 5 extension. You just annotate your JUnit 5 test class with @ExtendWith( JGivenExtension.class ) and JGiven is enabled.

If you just use the extension directly, you have to inject your stage classes by using the @ScenarioStage annotation.

@ExtendWith( JGivenExtension.class )
public class JUnit5Test {

   @ScenarioStage
   MyStage myStage;

   @Test
   public void my_scenario() {
      myStage
        .given().some_context()
        .when().some_action()
        .then().some_outcome();

   }
}

Alternatively, you can use one of the test base classes ScenarioTest, SimpleScenarioTest or DualScenarioTest that provide additional convenience methods and allows you to specify stage classes by using type parameters:

public class JGiven5ScenarioTest
    extends ScenarioTest<GivenStage, WhenStage, ThenStage> {

    @Test
    public void JGiven_works_with_JUnit5() {
        given().some_state();
        when().some_action();
        then().some_outcome();
    }
}

Please note that JGiven is built around each scenario, i.e. test method, having its own test instance. Therefore, we cannot support JUnit’s per-class test instance lifecycle. An exception will be thrown if any such attempt is made.

16.4. Example Project

You find a complete example project on GitHub: https://github.com/TNG/JGiven/tree/master/example-projects/junit5