Cucumber up to version 2 had a neat feature called
Step Argument Transforms
Show archive.org snapshot
which was dropped in favor of Cucumber 3
ParameterTypes
Show archive.org snapshot
. While I strongly encourage you to drop your legacy Transforms when upgrading to Cucumber 3, it might not always be possible due to their different design.
This is a guide on how to keep the exact same functionality of your old Transforms
while writing them in the style of new ParameterTypes
.
Why would I want to keep my Transforms?
Transform
s allowed you to globally transform step arguments based on a matcher pattern. Take a look at this Cucumber step:
Then I sleep for 3 seconds
If you chose to define a Transform
that matches and replaces all string numbers with an actual integers, it was also applied to the arguments of this step.
With Cucumber 3, you can twist the step above a little to use the new concept of ParameterType
s:
Then I sleep for {number} seconds
So, what is the problem?
You cannot integrate your ParameterType
s with steps that are defined in a gem like our
cucumber_factory
Show archive.org snapshot
. If you used to rely on Transform
s before upgrading to Cucumber 3, you have to backport the missing functionality.
Given there is a user with the work days "[monday, tuesday]"
Assuming Cucumber Factory
wouldn't have an array parsing feature
Show archive.org snapshot
, there would be no way to apply an {array}
ParameterType to this step provided by the gem.
Understanding Cucumber 3
The concept of a Transform
is no longer available
# No longer works. This used to replace the `[...]` string fragment with an actual array in the step 'I should see the items "[2, 3, 4]"'
Transform /^\[([^"\[\]]*)\]$/ do |array_string|
# Code to transform the string argument into a real Array instance object
end
Transform
s consisted of a regular expression that was matched against each step argument and a block that described the replacement on a match.
ParameterType
is the new way to go
# These definitions hook into steps like this: 'I should see the items "{array}"'
ParameterType(
name: 'array',
# Note: the RegEx no longer includes the ^ and $ delimiter:
regexp: /\[([^"\[\]]*)\]/,
type: Array,
transformer: lambda do |array_string|
# Code to transform the string argument into a real Array instance object
end
)
The big difference: ParameterTypes are only applied if explicitly specified in a step definition. This has usually the positive side effect that it forces you to write explicit code.
They can either be referred in step definitions based on their name in or with their exact regular expression:
# Step definition using a string with {ParameterType.name}
Then 'I should see the pagination elements "{array}"' do |elements|
# Code
end
# Step definition using a regular expression
Then /^I should see the pagination elements "\[([^"\[\]]*)\]"/ do |elements|
# Code
end
You can no longer use nested capture groups
The last example above shows that you lose verbosity if you chose to use a regular expression to write your step definition. What's worse: To provide this "feature" of replacing fragments of regular expressions with the transformation of a ParameterType
, Cucumber 3 ships with a homemade regular expression parser that is missing many standard features:
- Nested capture groups can no longer be used in step definitions.
- Even nesting inside a non-capturing group (
?:
) is broken **:Given /^I press the (?:(\d)st button)$/ { |index| .. }
is no longer valid.
The solution: Backport your Transforms
If you are determined to update your project while sticking to Transforms, e.g. because you are heavily using them with Cucumber Factory, you can add the attached files below to your project. You will need to use a new class called ParameterTypeProxy
to define your ParameterType
s, which in turn keeps it all DRY and in one place. Let's assume you just copied the files and want to migrate one of your Transforms, e.g. the "number" matcher from above.
1.: Create a new ParameterType
based on your existing Transform to "parameter_types.rb" using the Proxy class.
ParameterTypeProxy.add(
name: 'number',
regexp: /\d/,
type: Integer,
transformer: lambda(&:to_i)
)
2.: These few lines allow you to use the ParameterType in your step definitions:
Then I sleep for {number} seconds
2.1.: Bonus: You can also apply the ParamterType manually, e.g. in more complex step definitions.
Given /^I sleep for (\d) seconds( repeatedly)?$/ do |string_seconds, repeat|
real_seconds = ParameterTypeProxy.apply(:number, string_seconds)
end
3.: Only neccessary when using step definitions of gems (like Cucumber Factory): Add the transformation to the backported Transform
method in "legacy_transforms.rb":
LegacyTransforms.register /^\d$/ do |string_number|
ParameterTypeProxy.transform(:number, string_number)
end
Please note that based on their different design, the matcher expression of Transforms is slightly different than the one of ParameterTypes - they are matched against step arguments, not part of step definitions.
Attachments
-
parameter_type_proxy.rb
This file defines a classParameterTypeProxy
which can be used to register Cucumber 3ParameterTypes
. It provides methods for explicit application of registered transformations:
ParameterTypeProxy.apply(:array, '[monday, tuesday]')
-
parameter_types.rb
Use this file to add your newParameterTypes
(using the proxy). The attached file contains thearray
example described above. -
legacy_transforms.rb
This file adds aTransform
method to the Cucumber "World" Show archive.org snapshot which behaves similar to the Cucumber 1 and 2 transformations. This allows cucumber_factory Show archive.org snapshot to keep using your transformations.
You have to register every transformation you want to expose at the bottom of the file. The attached file contains thearray
example described above. Please note that all regular expression ofTransforms
must be wrapped with the delimiters/^$/
to avoid false positives.
Place these files in features/support/
.