cucumber_factory: How to keep using Cucumber 2 Transforms in Cucumber 3

Updated . Posted . Visible to the public.

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?

Transforms 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 ParameterTypes:

Then I sleep for {number} seconds

So, what is the problem?

You cannot integrate your ParameterTypes with steps that are defined in a gem like our cucumber_factory Show archive.org snapshot . If you used to rely on Transforms 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

Transforms 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 ParameterTypes, 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 class ParameterTypeProxy which can be used to register Cucumber 3 ParameterTypes. It provides methods for explicit application of registered transformations:
ParameterTypeProxy.apply(:array, '[monday, tuesday]')
  • parameter_types.rb
    Use this file to add your new ParameterTypes (using the proxy). The attached file contains the array example described above.

  • legacy_transforms.rb
    This file adds a Transform 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 the array example described above. Please note that all regular expression of Transforms must be wrapped with the delimiters /^$/ to avoid false positives.

Place these files in features/support/.

Michael Leimstädtner
Last edit
Michael Leimstädtner
License
Source code in this card is licensed under the MIT License.
Posted by Michael Leimstädtner to makandra dev (2019-05-29 16:00)