Tabular Input and Output in JBehave

Let's start with a story: Given a table of factors: |first|second| |4 |5 | |3 |2 | |8 |8 | When math is done Then I should have: |product|sum| |20 |9 | |6 |5 | |64 |16 |

We want to read in the values from the input table and check them against the expected values in the output table.

We'll need steps:

@Given("a table of factors:$factors)
public void factors(ExamplesTable inputTable) {
    inputData = inputTable;
}

The ExamplesTable is basically a List of rows stored as Maps keyed by the column headers.

There's two bits of magic. The first is storing the actual results in a List of Maps that uses the same keys as the headers of the output table, in this case "product" and "sum".

@When("math is done")
public void math() {
    results = new ArrayList<Map<String, Integer>>();
    for(Parameters parameter : inputData.getRowsAsParameters()) {
        // Get the columns by header
        int first = parameter.valueAs("first", Integer.class);
        int second = parameter.valueAs("second", Integer.class);
        HashMap<String, Integer> resultMap = new HashMap<String, Integer>();
        // put them in a map by out table header
        resultMap.put("product", first * second);
        resultMap.put("sum", first + second);
        results.add(resultMap);
    }
}

The second bit of magic is adding Hamcrest matchers to the OutcomesTable so JBehave can do the comparison then calling verify() on the table.

@Then("I should have:$resultTable")
public void results(ExamplesTable table) {
    OutcomesTable expected = new OutcomesTable();
    for (int i = 0; i < results.size();i++) {
        Parameters row = resultTable.getRowAsParameters(i);
        Map<String, Integer> actual = results.get(i);
        for (String key : actual.keySet()) {
            expected.addOutcome(key, actual.get(key), (Matchers.equalTo(row.valueAs(key, Integer.class))));
        }
    }
    expected.verify();
}

The parameters for addOutcome(key, actual, matcher) are

  • key: the header in the outcome table
  • actual: actual value determined in the When clause
  • matcher: a Hamcrest matcher to do the comparison

You don't have to store the intermediate actual values in List<Map> you just have to know how to load the OutcomesTable with the proper key values to match the out table headers. The list of maps just makes it convenient to keep them in sync.