I was exploring the new io.cucumber.datatable.DataTable support introduced in cucumber-jvm:3.0.0. I have cucumber-jvm:3.0.2 installed.
My existing cucumber-jvm:2.4.0 project makes use of a simple design pattern where I pass a table into a step whose last parameter has type Map. This behaviour is no longer supported in version 3.
So I created a test case to model an alternative to this design pattern. It works, but the Map that is returned is unmodifiable. I worked around it but two questions:
Here is the code:
# h/t to https://github.com/cucumber/datatable-java/edit/master/datatable/src/test/java/io/cucumber/datatable/DataTableTypeRegistryTableConverterTest.java
Feature: Test simple DataTable converters
@simple_no_header_map_string_string
Scenario: Convert a two column table to a Map
Given a basic street address
| streetAddress | 333 W Camden St |
| cityName | Baltimore |
| stateName | MD |
| postalCode | 21201 |
When I specify Country "US"
Then I can print the complete address
package some.org.stuff.cucumberjvm302testbed;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import io.cucumber.datatable.DataTable;
import io.cucumber.datatable.DataTable.TableConverter;
import io.cucumber.datatable.DataTableTypeRegistry;
import io.cucumber.datatable.DataTableTypeRegistryTableConverter;
import io.cucumber.datatable.TypeReference;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class Cucumber302TestBedSteps {
private Map<String,String> addressMap;
private final DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH);
private final TableConverter converter = new DataTableTypeRegistryTableConverter(registry);
private static final Type MAP_OF_STRING_TO_STRING = new TypeReference<Map<String, String>>() {
}.getType();
@Given("^a basic street address$")
public void aBasicStreetAddress(DataTable datatable) {
//Here is the workaround to make addressMap mutable
addressMap = new HashMap<String,String>(converter.convert(datatable, MAP_OF_STRING_TO_STRING));
}
@When("^I specify Country \"(.+)\"$")
public void iSpecifyCountry(String inputCountry) {
addressMap.put("country", inputCountry);
}
@Then("^I can print the complete address$")
public void iCanPrintTheCompleteAddress() {
System.out.println("\n\t" + addressMap.get("streetAddress"));
System.out.println("\t" + addressMap.get("cityName"));
System.out.println("\t" + addressMap.get("stateName"));
System.out.println("\t" + addressMap.get("postalCode"));
System.out.println("\t" + addressMap.get("country") + "\n");
}
}
I looked at Cucumber-JVM 3 - Convert DataTable to single object using asMap(). I did implement that solution but I have some junior testers so I was looking for something simpler.
You may find this pattern to be a bit more to your liking:
private final Map<String,String> addressMap = new HashMap<>();
@Given("^a basic street address$")
public void aBasicStreetAddress(Map<String,String> address) {
addressMap.putAll(address);
}
@When("^I specify Country \"(.+)\"$")
public void iSpecifyCountry(String inputCountry) {
addressMap.put("country", inputCountry);
}
@Then("^I can print the complete address$")
public void iCanPrintTheCompleteAddress() {
System.out.println("\n\t" + addressMap.get("streetAddress"));
System.out.println("\t" + addressMap.get("cityName"));
System.out.println("\t" + addressMap.get("stateName"));
System.out.println("\t" + addressMap.get("postalCode"));
System.out.println("\t" + addressMap.get("country") + "\n");
}
Rather then setting the addressMap in the given step we define it as an empty map first. This ensures that there is always a sensible default present. This makes steps more flexible because they can now be used in any order and multiple times. Admittedly of little value in this specific scenario but in general quite usefull.
Then in the given step we set the last parameter to Map<String,String> address
. Cucumber will transform the DataTable
into a map. We use .putAll
to copy all fields from the address
into the addressMap
.
The address
is immutable to prevent modification of the original input by methods with side effects. This is a useful mechanism when the original input is used later to make assertions. Unless the object was explicitly copied you can rely on the fact that it was not modified. This makes code easier to read.
My existing cucumber-jvm:2.4.0 project makes use of a simple design pattern where I pass a table into a step whose last parameter has type Map. This behaviour is no longer supported in version 3.
This behavior is still supported. What made you think it wasn't?
private final DataTableTypeRegistry registry = new DataTableTypeRegistry(Locale.ENGLISH); private final TableConverter converter = new DataTableTypeRegistryTableConverter(registry); private static final Type MAP_OF_STRING_TO_STRING = new TypeReference>() { }.getType();
You don't need this here. Have a look at the TypeRegistryConfigurer. Though keep in mind that Maps and Lists of basic types have build in support.