In the last three months we picked up the pace and managed to do a new release nearly every week. With v6-RC1 and v5.7.0 released now would be a good time to look back and see what is still relatively new in v5.
As always the full changelog can be in its usual place.
In v5.0.0 empty cells in a data table would be transformed into null rather than
the empty string. By using replaceWithEmptyString = "[blank]"
on a
datatable type an empty string can be explicitly inserted.
However, this did not work for when converting to build in types such as String
and Object
. It is now possible to redefine build in types.
This enables the following:
Feature: Whitespace
Scenario: Whitespace in a table
Given a blank value
| key | value |
| a | [blank] |
@given("A blank value")
public void givenABlankValue(Map<String, String> map){
// map contains { "key":"a", "value": ""}
}
@DataTableType(replaceWithEmptyString = "[blank]")
public String listOfStringListsType(String cell) {
return cell;
}
Note that this only applies to String
and Object
. It is not possible to
redefine other build in types. Though this could be considered if there is a
clear use case.
The Cucumber CLI is rather complex to use. To work correctly it needs both
a --glue
parameter in the form of a package name, and a location of a feature
file in form of a classpath uri or path. As a result people often configure
Cucumber incorrectly and are left wondering why their features or glue cannot
be found.
This can be simplified by using sensible default for both. Unless explicitly told otherwise Cucumber will assume that glue and feature files reside in the class path root. So with the following project layout the CLI can discover and execute all features without needing additional arguments.
├── pom.xml
├── src
│ ├── main
│ │ └── java
│ │ └── com/example/Application.java
│ └── test
│ ├── java
│ │ └── com/example/StepDefinitions.java
│ └── resources
│ └── com/example/example.feature
This can be done with a single maven command:
mvn exec:java \
-Dexec.classpathScope=test \
-Dexec.mainClass=io.cucumber.core.cli.Main
The JUnit Platform supports skipping tests. For example in JUnit Jupiter a
test can be annotated with @Disabled
. This test will be marked as skipped.
Cucumber scenarios do not have annotations nor is there any support to disable
specific scenarios. A typical work around from JUnit 4 was to set
tags="not @Disabled"
in @CucumberOptions
and tag scenarios with
@Disabled
.
To do the same with the Cucumber JUnit Platform Engine the
cucumber.filter.tags
property can be used. For example by adding
cucumber.filter.tags=not @Disabled
to junit-platform.properties
.
Note: unlike JUnit 4, skipped scenarios are not remove from in the test hierarchy.
When using regular expressions, Cucumber will use the pattern in a capture group as hint to determine which parameter type should be used.
When the type hint provided by the regex, and the type hint provided by the
method (i.e. the types of its arguments) disagree about the type
cucumber-expressions
prefers the hint provided by the regex.
When declaring parameters types with a very simple regular expressions this may cause a problem. For example:
import io.cucumber.java.ParameterType;
import io.cucumber.java.en.Given;
public class Main {
private static class TypeA {}
@ParameterType("\\w+")
public TypeA typeA(String a) {
return new TypeA();
}
@given("a cucumber expression of {typeA}")
public void an_object_of_type_a(TypeA a) {
// works fine!
}
@given("^a regular expression of (\\w+)$")
public void an_object_of_type_b(String b) {
// broken: will attempt to use the transformer for TypeA
}
}
By using @ParameterType(pattern="\\w+", useRegexpMatchAsStrongTypeHint=false)
this behaviour can be changed to instead prefer the type hint from the method.
Note: useRegexpMatchAsStrongTypeHint
will default to false
in v6.
Cucumber Spring has a complicated way to configure the application context.
The configuration can be provided by either:
- A context configuration annotation on a class which also happens to have step definitions
- A magic file named
cucumber.xml
- An empty application context if the previous options could not be discovered
This makes it hard to explain how to use Cucumber Spring and the fallback strategy to the empty application context hides errors.
From now on the preferred way to use cucumber-spring
is to annotate a class
with both @CucumberContextConfiguration
and a Spring context configuration
annotation such as @ContextConfiguration
, @SpringBootTest
, ect.
import com.example.app;
import org.springframework.boot.test.context.SpringBootTest;
import io.cucumber.spring.CucumberContextConfiguration;
@CucumberContextConfiguration
@SpringBootTest(classes = TestConfig.class)
public class CucumberSpringConfiguration {
}
The alternatives, cucumber.xml
and annotating step definitions with a
@ContextConfiguration
have been deprecated and will be removed in v6.
The JUnit Platform supports parallel execution. To avoid flakey tests when multiple scenarios manipulate the same resource tests can be synchronized on that resource.
To synchronize a scenario on a specific resource the scenario must be tagged and this tag mapped to a lock for a specific resource. A resource is identified by a string and can be either locked with a read-write-lock, or a read-lock.
For example:
Feature: Exclusive resources
@reads-and-writes-system-properties
Scenario: first example
Given this reads and writes system properties
When it is executed
Then it will not be executed concurrently with the second example
@reads-system-properties
Scenario: second example
Given this reads system properties
When it is executed
Then it will not be executed concurrently with the first example
With this configuration:
cucumber.execution.exclusive-resources.reads-and-writes-system-properties.read-write=SYSTEM_PROPERTIES
cucumber.execution.exclusive-resources.reads-system-properties.read=SYSTEM_PROPERTIES
The first scenario tagged with @reads-and-writes-system-properties
will lock
the SYSTEM_PROPERTIES
with a read-write lock and will not be concurrently
executed with the second scenario that uses a read lock.