Tabular Parameters

Scenario writers may want to express parameters in a tabular structure. For example:

Given the traders:
|name|rank|
|Larry|Stooge 3|
|Moe|Stooge 1|
|Curly|Stooge 2|
When Traders are subset to ".*y" by name
Then the traders returned are:
|name|rank|
|Larry|Stooge 3|
|Curly|Stooge 2|

JBehave supports multi-line parameters out-of-the-box and the user only needs to declare the parameter type as ExamplesTable for it to be automatically parsed as a tabular structure:

@Given("the traders ranks are: $ranksTable")
public void theTraders(ExamplesTable ranksTable) {
    this.ranksTable = ranksTable;
    this.traders = toTraders(ranksTable);
}
 
private List toTraders(ExamplesTable table) {
    List traders = new ArrayList();
    for (Map<String,String> row : table.getRows()) {
        String name = row.get("name");
        String rank = row.get("rank");
        traders.add(new Trader(name, rank));
    }
    return traders;
}

Parametrised tables

Sometimes, you may want to parametrise the table row values and replace them with named parameters, e.g. as specified in the Examples table and changing for each parametrised scenario:

Given that Larry has done <trades> trades
Then the traders activity is:
|name|trades|
|Larry|<trades>|
|Moe|1000|
|Curly|2000|
 
Examples:
|<trades>|
|3000|
|5000|

The ExamplesTable provide an option to specify whether the named parameters should be replaced in the values when retrieving the rows as parameters:

@Then("the traders activity is: $table")
public void theTraderActivityIs(ExamplesTable table) {
    boolean replaceNamedParameters = true;
    int trades = table.getRowAsParameters(0, replaceNamedParameters).valueAs("trades", int.class);
    // expect 3000
}

Row values as converted parameters

Besides retrieveing the row values as String name-value pairs, the user retrieve them as Parameters, which allow the values to converted to the desidered type, using the same ParameterConverters defined in the stories configuration:

Then the traders activity is:
|name|trades|
|Larry|3000|
|Moe|1000|
|Curly|2000|
@Then("the traders activity is: $activityTable")
public void theTradersActivity(ExamplesTable activityTable) {
    for (Parameters row : rows.getRowsAsParameters()) {
        Trader trader = row.valueAs("name", Trader.class);
        int trades = row.valueAs("trades", Integer.class);
        System.out.println(trader.getName() + " has done " + trades + " trades");
    }
}

In order not to repeat values in tabular structures, defaults are supported that allow re-use of previous defined tables:

@Then("the current trader activity is: $activityTable")
public void theTradersActivityIs(ExamplesTable activityTable) {
    for (int i = 0; i < activityTable.getRowCount(); i++) {
        Parameters row = activityTable.withDefaults(this.ranksTable.getRowAsParameters(i)).getRowAsParameters(i);
        System.out.println(row.valueAs("name", Trader.class).getName() + " ("+row.valueAs("rank", String.class, "N/A")+") has done " + row.valueAs("trades", Integer.class) + " trades");           
    }
}

In this example, the row parameters are the union (for the corresponding row number) of the ranks and activity tables. Note that any existing value in the row map of data will not be overridden.

Finally, the Parameters facade also allows for specification of a default value if the parameter name is not found:

@Then("the current trader activity is: $activityTable")
public void theTradersActivityIs(ExamplesTable activityTable) {
    Parameters row = activityTable.getRowAsParameters(0);
    String name = row.valueAs("name", String.class);
    String organisation = row.valueAs("organisation", String.class, "N/A");
    System.out.println(name + " is part of organisation: " + organisation);
}

Mapping parameters to custom types

It may sometime be useful to map the table row parameters to a custom object. This can be done by annotating the type with the AsParameters annotation:

@AsParameters
public static class MyParameters {
    @Parameter(name = "aString")
    private String string;
    @Parameter(name = "anInteger")
    private Integer integer;
    @Parameter(name = "aBigDecimal")
    private BigDecimal bigDecimal;
}

The fields can be optionally mapped to the parameter names via the Parameter annotation.

If not present, the default mapping will use the name of fields to match the name of the parameters.

Once we've defined our custom parameters type, we can inject it in a method requiring one or more of these types:

@Given("the parameters mapped via names to custom types: %table")
public void givenTheNamedParametersList(List<MyParameters> list) {
    System.out.println("List named: "+list);
}
 
@Given("the parameters mapped via names to custom type: %table")
public void givenTheNamedParametersType(MyParameters single) {
    System.out.println("Single named: "+single);
}

If we cannot control the custom type, e.g. we want to map to a type contained in a third-party library, we can still achieve the mapping programmatically:

@Given("the parameters mapped via names to custom types: %table")
public void givenTheNamedParametersList(ExamplesTable table) {
    Map<String, String> nameMapping = new HashMap<String, String>();
    nameMapping.put("aString", "string");
    nameMapping.put("anInteger", "integer");       
    List<ThirdPartyParameters> parameters = examplesTable.getRowsAs(ThirdPartyParameters.class, nameMapping);
    ...
}

Convert row to type using custom converter

It may sometime be useful to map the whole row to a custom object using custom converter rather than relying on field to field mapping

Given the following class that we want to convert from Parameter

public class Person {
    private int age;
    private String name;
 
    ...
}

Create parameter converter for the Person class, please do not forget to register converter in ParameterConverters

public class PersonConverter extends AbstractParameterConverter<Parameters, Person> {
    @Override
    public Person convertValue(Parameters value, Type type) {
        Person person = new Person();
        ...
        return person;
    }
}

Use as(java.lang.reflect.Type) method to perform the conversion

@Then("the person age is greater than 18: $activityTable")
public void thePersonAge(ExamplesTable activityTable) {
    Parameters row = activityTable.getRowAsParameters(0);
    Person person = row.as(Person.class);
    System.out.println("Age allowed: " + person.getAge() > 18);
}

Preserving whitespace

By default, value in the table are trimmed, i.e. any preceding and trailing whitespace is removed. This is the most useful and common usecase. If, for some reason, you need to preserve the whitespace, you can specify an optional parsing property:

{trim=false}
|name |rank    |
|Larry|Stooge 3|
|Moe  |Stooge 1|
|Curly|Stooge 2|

Specifying inlined separators

The separators are also configurable via inlined properties:

{ignorableSeparator=!--,headerSeparator=!,valueSeparator=!,commentSeparator=#}
!header 1!header 2! .... !header n!
!-- An ignored row --!
!value 11#comment in value!value 12! .... !value 1n!
...
!-- Another ignored row --!
!value m1!value m2! .... !value mn!

Mapping values to null-s

By default all empty values in ExamplesTable are treated as empty strings. However it might be required to map certain values to null-s. It can be done at the step implementation level or by applying the generic approach at the table level:

{nullPlaceholder=NULL}
|header |
|value 1|
|NULL   |
|value 3|

Using values with line breaks

Line break is a default separator for rows in ExamplesTable, that's why they can't be added as is to the data. In order to put the value with line breaks to ExamplesTable escape sequences (a character preceded by a backslash (\) is an escape sequence) must be used.

Escape SequenceDescription
\nInsert a newline in the value at this point.
\rInsert a carriage return in the text at this point.
\\Insert a backslash character in the text at this point.
Inlined property processEscapeSequences defines whether escape sequences should be replaced in the data. It’s false by default (no property is declared explicitly). The allowed values are true and false, any other values are considered invalid and will lead to exception thrown at parsing.
{processEscapeSequences=true, commentSeparator=#}
|header          |
|line 1\nline 2  |# The value with a newline
|line 1\r\nline 2|# The value with a carriage return and a newline
|line 1\\nline 2 |# The value with an escaped escape sequence, the result will be "line 1\nline 2"

Using table transformers

Table transformers allow the table to be transformed via an inlined property. E.g. to transform from a landscape table form we can to use the pre-registered transformer:

{transformer=FROM_LANDSCAPE}
|header 1|value 11| ... | value m1|
...
|header n|value 1n| .... !value mn!
Or one can define our own custom transformer
{transformer=FROM_MY_FORMAT}
...
Also chain of transformers can be defined. In this case the transformers are applied sequentially from top to bottom.
{transformer=FROM_LANDSCAPE}
{transformer=FROM_MY_FORMAT}
...
In this case the custom transformer needs to be registered by name with the TableTransformers via the ExamplesTableFactory:

TableTransformers tableTransformers = new TableTransformers();
tableTransformers.useTransformer("FROM_MY_FORMAT", new TableTransformer(){
 
    public String transform(String tableAsString, Properties properties) {
        return ...; // transform as required
    }
     
});
ExamplesTableFactory tableFactory = new ExamplesTableFactory(tableTransformers);

The custom table factory then needs to be configured to used by the parameter converters and the story parser:

new MostUsefulConfiguration()
        .useParameterConverters(new ParameterConverters().addConverters(new ExamplesTableConverter(tableFactory)))
        .useStoryParser(new RegexStoryParser(tableFactory))
In addition, there is the possibility to escape commas and braces in transformer properties:
{propertyKey=\{innerKey1=innerValue1\, innerKey2=innerValue2\}}

Loading tabular parameter from an external resource

The tabular parameter can also be loaded from an external resource, be it a classpath resource or a URL.

Given the traders: org/jbehave/examples/trader/stories/traders.table   

Inlined properties can be applied to the tabular parameter loaded from an external resource

Given that Larry has done <trades> trades
Examples:
{headerSeparator=!,valueSeparator=!}
org/jbehave/examples/trader/stories/traders.table

We need to enable theExamplesTable parameter converter to find the resource with the appropriate resource loader configured via the ExamplesTableFactory

:
new ParameterConverters().addConverters(new ExamplesTableConverter(new ExamplesTableFactory(new LoadFromClasspath(this.getClass()))))

Modifying content of tabular parameter

When using ExamplesTable to process tabular parameter data, it may be useful to allow additions of columns to the table for a given row, e.g. to express a result value for given row. The non-affected rows would then be given default blank values for the new column.

ExamplesTable table = ... // provided as parameter
int row = 0;
Map<String,String> rowValues = ... // a map holding the new or updated values
table.withRowValues(row, rowValues);

Equally, we may want to start from the content data (list of maps) and create a new table with modified content.

ExamplesTable table = ... // provided as parameter
List<Map<String,String>> content = table.getRows();
// modify content as necessary
ExamplesTable updatedTable = table.withRows(content);

Once modified, the table can be written to an output:

ExamplesTable table = ... // provided or modified
table.outputTo(new PrintStream(new FileOutputStream(new File("output.table))));

In other words, ExamplesTable can be used for both the string->content and the content->string transformations when implementing a step with tabular parameters.

Under the hood, JBehave users the same table parsing functionality of the parametrised scenarios, but there is a fundamental difference between these two use cases: with parametrised scenarios, the scenario is run for each line of the table (using in each execution the parameter values of the given row), while in using tabular parameters we are simply using the tabular structure as a parameter, and how this structure is interpreted is up to the scenario writer. The difference is in the Examples: keyword, which is only present for parametrised scenarios.

Working with self-referencing rows in example table

By default, JBehave does not support self-referencing relationship in example table rows (when a column value in a row relates to another column in the same row). To resolve such mechanism can be used the transformer for resolving self references which must first be registered in your Configuration:

TableTransformers.ResolvingSelfReferences resolvingSelfReferencesTransformer = new TableTransformers.ResolvingSelfReferences(parameterControls);
TableTransformers transformers = tableTransformers();
transformers.useTransformer("RESOLVING_SELF_REFERENCES", resolvingSelfReferencesTransformer);

Usage example:

{transformer=RESOLVING_SELF_REFERENCES}
|header1 |header2          |header3          |
|value1  |<header1>-value2 |<header2>-value3 |

The transformer enables resolution of references to one column from another column within the one row of the example table. Circular references will result in error.
The table from the example above will be transformed into the following:

|header1 |header2       |header3              |
|value1  |value1-value2 |value1-value2-value3 |