Posted about 1 year ago. Visible to the public.

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

Cucumber up to version 2 had a neat feature called Step Argument Transforms which was dropped in favor of Cucumber 3 ParameterTypes. 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. 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, 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 {} 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.


  • 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" which behaves similar to the Cucumber 1 and 2 transformations. This allows cucumber_factory 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/.

By refactoring problematic code and creating automated tests, makandra can vastly improve the maintainability of your Rails application.

Owner of this card:

Michael Leimstädtner
Last edit:
about 1 year ago
by Besprechungs-PC
legacy_transforms.rb, parameter_type_proxy.rb, parameter_types.rb
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Michael Leimstädtner to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more