-
Notifications
You must be signed in to change notification settings - Fork 0
Step Argument Transforms
Step argument transforms help your step definitions be more by DRY by allowing you to refactor common operations performed on the step definition arguments to a common operation. Before each step definition’s captured Regexp groups are yielded as arguments to the step definition block, an attempt is made to match them against registered Transform objects. If a capture value matches one of the Transform objects the result of the Transform block yields to step definition as the argument.
# features/step_definitions/user_steps.rb
Transform /^(-?\d+)^/ do |number|
number.to_i
end- Uses the keyword
Transform - Has a regular expression that is compared against all the captures of the step definition
- Implements a block whose result is sent to the step definition block as the matching argument
The following transform converts any step definition captures that match digits and converts them automatically to integers.
# features/step_definitions/user_steps.rb
Transform /^(-?\d+)^/ do |number|
number.to_i
end
Then /^a user, named '([^']+)', should have (\d+) followers$/ do |name,count|
# Without the transform your count object would be a string and not a number
count.should be_kind_of(Numeric)
#...
end# features/table.feature
Feature: Users
Scenario: Creating Users
Given the following users:
| user | followers |
| aslakhellesoy | 317 |
| jberkel | 51 |
| bfaloona | 4 |
| mattwynne | 33 |Table objects have two extremely useful methods: map_column! and map_headers!.
# features/step_definitions/user_steps.rb
Given /^the following customers$/ do |table|
# Convert all headers to lower case symbol
table.map_headers! {|header| header.downcase.to_sym }
# Convert all user strings to Users
table.map_column!(:user) {|user| User.find_by_name(user) }
# Convert all the digits in the followers column to an Integer
table.map_column!(:followers) {|count| count.to_i }
table.hashes.each do |row|
row[:user].followers = row[:followers]
end
endA table like this one may occur again in a creation step or a validation step and Transforms will again help keep you DRY. Transforms can also be used with tables. A table transform is matched via a comma-delimited list of the column headers prefixed with ‘table:’
# features/step_definitions/user_steps.rb
Transform /^table:user,followers$/ do |table|
table.map_headers! {|header| headers.downcase.to_sym }
table.map_column!(:user) {|user| User.find_by_name(user) }
table.map_column!(:followers) {|count| count.to_i }
table
end
Given /^the following users$/ do |table|
table.hashes.each do |row|
row[:user].followers = row[:followers]
end
end
Then /^I expect the following users$/ do |table|
table.hashes.each do |row|
row[:user].should_not be_nil
row[:user].followers.should == row[:followers]
end
endTransforms are powerful and it is important to take care how you implement them as mistakes with them can often introduce strange and unexpected behavior.
1. Transforms always require that you specify a block variable even if you do not specify any groups to capture.
With no capture groups specified the entire value is returned as a string to the block. This is contrast to how step definitions perform and can often trip you up. Cucumber will fail execution with a warning when you fail to specify at least one variable.
# Valid - without the capture group () specified
Transform /^-?\d+^/ do |number|
number.to_i
end
# Invalid - Cucumber will raise an error when you execute the test suite
Transform /^-?\d+^/ do
number.to_i
endWhen you specify capture groups, each capture will be yielded to the block as you have likely come to expect from step definitions would expect.
# Converts the numeric abbreviation for KB/MB to the size
Transform /^(\d+)\s?(KB|MB)$/ do |quantity,unit|
quantity.to_i * (unit =~ /MB/ ? 1000000 : 1000)
end2. Ensure that you specify the caret (^) and dollar sign ($) at the start and end of the regular expression.
The following transform, adds onto the integer conversion before it, treats an ‘a’ or ‘an’ as the integer value of 1. This may be useful in step definitions where you might want to say something like “a user” instead of “1 user”.
# Convert 'a' or 'an' to the integer value 1
Transform /^(an?|-?\d+)$/ do |amount|
amount =~ /an?/ ? 1 : amount.to_i
endHowever, removing the caret and dollar sign will allow this step definition to match ANY step definition captures that have the value a in it. This will likely wreak havoc as the value 1 starts appearing in a large set of your step definitions.
3. In step definition capture contextual to ensure that the correct transform is used.
Transform /^a user,? named '([^']+)',?$/ do |name|
User.find_by_name(name)
end
# i.e. Then a user, named 'aslakhellesoy', should have 317 followers
Then /^(a user, named '(?:[^']+)',) should have (\d+) followers$/ do |name,count|
user.should_not be_nil
user.should be_kind_of(User)
user.friends.should == count.to_i
endBefore using a transform, you would have likely only wanted to match the name, the value in single quotes, as a group However, matching only the name would likely open our transform up to a large number of arguments and we wouldn’t be assured that we were getting a user.
4. Register a Transform in the same file in which they are being employed.
If you find that you transform is used by multiple step definitions, move the transform to a file in the support directory (i.e. step_definitions/support/numeric_transforms.rb).