Cucumber-jvm 3 migration of PARAMETER conversion from version 2

Introduction

XStream in Java Cucumber implementation is dead. No more trying to decide whether to extend AbstractSingleValueConverter or implement Converter. This has been replaced by the concept of ParameterType and DataTableType.

For the official announcement mentioning other goodies, wander to this link https://cucumber.io/blog/2018/05/19/announcing-cucumber-jvm-3-0-0.

For the release notes navigate to https://github.com/cucumber/cucumber-jvm/blob/master/CHANGELOG.md and scroll down to the 3.0.0-SNAPSHOT section. In this check out point 2 where annotations like @Delimiter, @Format, @Transformer,@XStreamConverter, @XStreamConverters are laid to rest. These must be replaced by a DataTableType or ParameterType.

For details on Cucumber Expressions which have been introduced to work alongside Regular Expressions refer to this https://docs.cucumber.io/cucumber/cucumber-expressions/

Source Code

The migrated code for version 3.x is located here. The code for version 2.x is located here.

ParameterType

In Cucumber 2.x, XStream automatically handles the conversion of a parameter into the desired class. The only requirement is that an appropriate single argument constructor should be present in the destination class. For example the stepthe user name is John Doe – is to be converted into a User object. The step definition has the following pattern – ^the user name is (.*?)$.

Now if we use this same piece of code in Cucumber 3.x we will get an error similar to this.

cucumber.runtime.CucumberException: Failed to invoke …………., caused by java.lang.IllegalArgumentException: argument type mismatch

If one uses the following Cucumber Expression for the step definition patternthe user name is {user} – get this error.

cucumber.runtime.CucumberException: Could not create a cucumber expression for 'the user name is {user}'. It appears you did not register parameter type.

All that the error is saying, Cucumber needs some idea of how to convert from string to the object. And this will need to be registered.

Let us look at Cucumber 3 code by adding a ParameterType for conversion. One can find the complete code for the step, step definition, destination class and parameter registration at the linked locations.

public class Configurer implements TypeRegistryConfigurer {
	@Override
	public void configureTypeRegistry(TypeRegistry registry) {
		registry.defineParameterType(new ParameterType<>("user", ".*?", 
                User.class, User::new));
	}
}

It is very important that the class Configurer needs to be placed inside the package structure mentioned in the glue option given inside @CucumberOptions. Registration of all ParameterType and DataTableType will happen inside the configureTypeRegistry method.

What is ParameterType? – This contains all the mapping details and the transformation code for converting a string variable into a desired object. Let us look at the ParameterType constructor in more detail.

ParameterType
"user",     -> Maps to the pattern mentioned in the stepdefinition expression {user}
".*?",      -> Regular expression for matching
User.class, -> Desired object class
User::new   -> Transformation code, kind of similar to what is written in unmarshal() of custom XStream converter

This will output the same result as in the earlier cucumber versions.

With the ParameterType know how we can look at migrating from annotations like Delimiter, Format and Transform.

Migration of Enum

What happens in the case of an enum? In Cucumber 2.x, XStream will automatically convert it into the desired enum. The Cucumber 2.x code details – stepstep definition and destination enum.

In Cucumber 3.x, a ParameterType has to be registered for this conversion. One can find the complete code for the step, step definitiondestination enum and parameter registration at the linked locations.

Given the professor level is Associate

@Given("the professor level is {level}")
public void theProfessorLevelIs(ProfLevels level) {}

registry.defineParameterType(new ParameterType<>("level", ".*?", ProfLevels.class, 
        (String s) -> ProfLevels.valueOf(s.toUpperCase())));

Migrating from Delimiter annotation

This was a convenient way to convert a delimited string into a list of strings or objects in Cucumber 2.x. The Cucumber 2.x code details for converting a delimited string into a list of stringsstep and step definition. For converting a delimited string into list of objectsstep and step definition.

In Cucumber 3.x, we need to register a ParameterType which maps to ‘names’, takes a regular expression like ‘.*?’, returns a List and a transformation method to return the same. One can find the complete code for the step, step definition and parameter registration at the linked locations.

Given the user names are jane,john,colin,alice

@Given("the users are {names}")
public void givenNames(List<String> names) {}

registry.defineParameterType(new ParameterType<>("names", ".*?", List.class, (String s) -> Arrays.asList(s.split(","))));

In case, we need to get a List of objects, say of a User, we can add the code in the transformation method. Simply register a new ParameterType which maps to {users}. One can find the complete code for the step, step definition, destination class and parameter registration at the linked locations.

Given the users are jane,john,colin,alice

@Given("the users are {users}")
public void givenUsers(List<User> names) {}

registry.defineParameterType(new ParameterType<>("users", ".*?", List.class, (String s) -> Arrays.asList(s.split(","))
        .stream().map(User::new).collect(Collectors.toList())));

Migrating from Format annotation

This gave Cucumber 2.x a hint about how to transform a String into an object such as a Date or a Calendar. The Cucumber 2.x code details – step and step definition.

In Cucumber 3.x, we need to register a ParameterType which maps to ‘date_iso_local_date_time’, takes a regular expression like ‘.*?’, returns a java.time.LocalDateTime and a transformation method to return the same.  One can find the complete code for the step, step definition and parameter registration at the linked locations.

Given the date is 2017-11-05T09:54:13

@Given("the date is {date_iso_local_date_time}")
public void the_date_is(LocalDateTime date) {}

registry.defineParameterType(new ParameterType<>("date_iso_local_date_time", "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}", 
        LocalDateTime.class, (String s) -> LocalDateTime.parse(s)));

Migrating from Transform annotation

Have a look at the example in the ParameterType introduction above of converting the string in the feature file to a User object. If there is an appropriate single argument constructor, then XStream converts it automatically. All we need to do is add the parsing code to the specific constructor.

What happens when we do not have a single argument constructor? In Cucumber 2.x, way out is to use the Transform annotation in the stepdefinition which mentions the transformer class to use. The transformer class needs to extend the Transformer class and overrides the transform method. The Cucumber 2.x code details – stepstep definition, destination class and transformer class.

In Cucumber 3.x, we just need to register a ParameterType to hold this logic of converting string to a FullName object. The parsing and object creation code that was in the transformer moves to the ParameterType constructor. Or even move it to a class and refer to it by method reference. One can find the complete code for the step, step definition, destination class and parameter registration at the linked locations.

Given the name is 'John Mich Arthur Doe'

@Given("the name is {fullname}")
public void theColorIs(FullName fullName) {}

registry.defineParameterType(new ParameterType<>("fullname", ".*?", FullName.class, FullName::parseNameDetails));

Migrating from XStream conversion

For the case similar to the situation in which a Transform annotation is used in Cucumber 2.x, one can also provide a custom XStream converter. We need to inform Cucumber how to find this converter by using the @XStreamConverter or @XStreamConverters annotation. The Cucumber 2.x code details – stepstep definition, destination class with xtream annotation  and custom xstream class.

The @XStreamConverter is placed on the data object class. In case of no access to the data object class, the @XStreamConverters can be placed on the runner class containing multiple @XStreamConverter annotations.

@XStreamConverter(value = ProfessorXStreamConverter.class)
public class Professor { }

@XStreamConverters({
	@XStreamConverter(value = ProfessorXStreamConverter.class)
	//,@XStreamConverter(value = OtherXStreamConverter.class)  
        //Additional converters.})
@RunWith(Cucumber.class)

In Cucumber 3.x, the migration technique is similar to that for the Transform annotation, by registering a new ParameterType. One can find the complete code for the step, step definition, destination class and parameter registration at the linked locations.

Given the professor is John Doe 

@Given("the professor is {professor}")
public void theProfessorNameIs(Professor prof) { }

public static Professor parseProfessor(String name) {
        Professor prof = new Professor();
	prof.setProfName(name);
	return prof;
}

registry.defineParameterType(new ParameterType<>("professor", ".*?",Professor.class,Professor::parseProfessor));

Leave a Reply

Your email address will not be published. Required fields are marked *