Cucumber is a great tool that lets you create something akin to a personalized programming language for testing. If you haven’t heard of it yet, refer to previous posts on basic and advanced cucumber. While I love what Cucumber lets you do, up until now a lack of modularity within step definitions has been the elephant in the room. Dave Astels and I were lucky enough to stumble upon a neat solution to this while pairing recently, so let’s take a more specific look at the problem and solution. Note that at the time of writing the feature is only available in the trunk Git version of Cucumber, but expect a release soon.
Problem: Step Definition Arguments Captured as Strings
As you write step definitions that capture variable data in the provided regex, you’ll remain happy if the target data is naturally represented as a string. The following example uses DataMapper as its ORM and dm-sweatshop (found in dm-more) for fixture factories:
# step definition file Given /^a new user with username (.+)$/ do |username| @user = User.make(:username => username) end Then /^the user is valid$/ do @user.should be_valid end # feature file Scenario: valid username Given a new user with username larrytheliquid Then the user is valid
However, there are many occasions where the natural data-type for a captured argument may be something other than a string:
# step definition file Given /^a new user with age (\d+)$/ do |age_string| @user = User.make(:age => age_string.to_i) end Then /^the user is valid$/ do @user.should be_valid end # feature file Scenario: valid age Given a new user with age 22 Then the user is valid
The key thing to notice is that it was necessary to convert age_string to an integer because the concept of age is naturally represented here as an integer. While the conversion does not look like much work now, the actual problem lies in modularity. If we would like to refer to age in any subsequent step definitions, we must duplicate the code to coerce the string everywhere… certainly not DRY.
The code we’ve seen so far is able to get away with going from captured argument string, to the argument with its natural data-type, to an ORM instance representation, without too much pain. This is possible because the structure of the scenarios being written is fairly simple and setup can be performed in a Given that creates an instance variable to be used in a Then. However, there are more complex scenarios where this pattern is not possible.
# step definition file Given /^a customer user named (\w+)$/ do |username| User.gen(:customer, :username => username) end Given /^a support user named (\w+)$/ do |username| User.gen(:support, :username => username) end When /^user (\w+) is assigned user (\w+)$/ do |support_username, customer_username| support_user = User.first(:username => support_username) customer_user = User.first(:username => customer_username) support_user.assign(customer_user) end Then /^user (\w+) should be in user (\w+)'s work queue$/ do |customer_username, support_username| support_user = User.first(:username => support_username) customer_user = User.first(:username => customer_username) support_user.work_queue.should include(customer_user) end # feature file Scenario: support assigned a customer Given a support user named stoltini And a customer user named larrytheliquid When user stoltini is assigned user larrytheliquid Then user larrytheliquid should be in user stoltini's work queue
The example above needs to reference specific users in both a When and a Then, and cannot rely upon a single instance variable that sets up state in a Given. The unfortunate result is some pretty nasty duplication of the ORM finder code to lookup each user by their respective usernames in the database.
Solution: Step Argument Transforms
The historical problem with Cucumber has always been that it was restricted to yielding strings as step definition arguments, but this is no longer the case. With a new Transform method, we are able to register regular expressions with Cucumber that it will check against arguments before they are yielded to step definitions. In addition to a regex, Transform expects a block that will be passed the raw argument, and whose return value will be used in place of it.
First, lets revisit our original modularity problem in the age example:
# support file Transform /^age \d+$/ do |step_arg| /(\d+)$/.match(step_arg)[0].to_i end # step definition file Given /^a new user with (age \d+)$/ do |age| @user = User.make(:age => age) end Then /^the user is valid$/ do @user.should be_valid end
If you look at the Given, you’ll see that we expanded the capture group to include “age” as valuable contextual information. With step argument transforms, such contextual information is important to avoid overly general transforms that affect every argument.
The first argument to Transform uses a regex that anchors the beginning and end. This means that we will only match that specific entire string, rather than accidentally matching other step arguments that happen to contain a partial piece of our regex (of course, you could have more general versions if you wanted to, just tread carefully).
If a registered transform matches an argument of a step definition, that argument will be passed to the block supplied with the transform definition. In the Transform example above, we anchor at the end and just capture the digit, because we already know the structure of our input based on the initial match.
After pulling the information we want out of the match group, we apply our transform, to_i, which is yielded to the Given as the variable age. Note that we chose the name age in our new Given instead of age_string because we are expecting the transform to be applied. Most importantly, any other step definition that captures a group of the form (age \d+) will happily transform age into its natural type, keeping our code nice and DRY. Let’s see how step argument transforms change our previous more complex scenario.
# support file Transform /^user \w+$/ do |step_arg| User.first :username => /(\w+)$/.match(step_arg)[0] end # step definition file Given /^a customer user named (\w+)$/ do |username| User.gen(:customer, :username => username) end Given /^a support user named (\w+)$/ do |username| User.gen(:support, :username => username) end When /^(user \w+) is assigned (user \w+)$/ do |support_user, customer_user| support_user.assign(customer_user) end Then /^(user \w+) should be in (user \w+)'s work queue$/ do |customer_user, support_user| support_user.work_queue.should include(customer_user) end
Here we use a similar strategy to capture groups of the form (user \w+). The transform applied looks up the user by their username and returns a DataMapper instance. The neat thing is that we can reuse our capture group across multiple different step definitions (the When and the Then), and the more involved duplicated boilerplate code gets packed away in the call to Transform.
Tips and Tricks
Scenario outlines and example tables are really cool features of Cucumber that let you specify a lot of different permutations of data in a compact way. However, the feature is also somewhat limited, because it can only yield string data. With step argument transforms, you’ll find yourself using the awesome tables more because duplicated transform code is removed so there is less friction to write additional step definitions.
# feature file Scenario Outline: username validity Given a new user with age <age> Then the user is <validity> Scenarios: valid | age | validity | | 18 | valid | | 21 | valid | | 49 | valid | | 120 | valid | Scenarios: invalid | age | validity| | 0 | invalid | | 1 | invalid | | 12 | invalid | | 17 | invalid | #... plus different steps using age
Sometimes it may be more convenient to pass a string version of a regex to Transform, so this is supported. Below is an example where the goal is to test properties of a Unix system. Any capture groups that contain path followed by a Unix path are desired to be expanded into their absolute system filepath. The UNIX_PATH_CAPTURE pattern is designed to be regex-interpolated into other regex capture groups, so it is defined as a string to prevent unintentional use as a standalone regex.
# support file UNIX_PATH_CAPTURE = 'path (?:\w+|\/|\.|-|~)+' Transform UNIX_PATH_CAPTURE do |step_arg| File.expand_path /^path (.*)/.match(step_arg)[0] end
To avoid overly confusing dependencies, a step argument may only be transformed once. The Transform defined last gets matching order precedence over previously defined transforms, giving you the ability to “override” previous transforms. As a rule of thumb, define general transforms first and get more specific last. More importantly, appropriately including contextual data in capture groups prevents potentially unexpected transforms.
Conclusion
Cucumber has been a fantastic and innovative tool thus far. With step argument transforms, another bit of frustration is removed and your step definitions stay DRY.
As a final note, I’d like to point out how awesome it was to hack out the first version of this with Dave Astels while pairing, test-driven, and in less than an hour… given his BDD/RSpec/Cucumber background =) As mentioned before, we were pairing and ran into a problem that unearthed this feature. Before we knew it the console flashed from red to green.
Happy hacking!

Watch a Live Demo of Engine Yard AppCloud
The Engine Yard Newsletter
Larry,
forgive if this sounds naive (I am a novice in both ruby, and cucumber), but why didn't you guys favor an implementation, where you would "overload" the the regex result and allows something like this:
a) trivial case:
Given /^a new user with age (d+)$/ do | age.to_i |
b) non-trivial case:
When /^user (w+) is assigned user (w+)$/ do |support_user.to_user, customer_user.to_user|
# framework, deep inside cucumber
overload magic for String(????) or RegexResult(????)
#supporting file
def to_user
User.find_by_name….
end
Or is this something that is not possible in ruby/rails?? Again, sorry if this sounds all to naive, but just from a convention/readability point of view, I thought transforming the result itself seems easer to understand?
Just saying … JJ
Hi JJM,
If what you suggested were possible, then you would still not keep code DRY because you would need to duplicate the various #to_whatever calls in each step definition.
In Ruby, you can extend the String class to add extra #to_whatever methods like you suggested, but you can't make method calls inside the block parameters like you showed.
I am sold – thanks for the clarification!
- JJ
For simple cases where you do need to parse the entire step argument, you can include capture groups that get yielded as arguments instead:
Transform /^age (\\d+)$/ do |age_string|
age_string.to_i
end
Transform /^user (\\w+)$/ do |username|
User.first :username => username
end
Also, as of Cucumber 0.3.101 step argument transforms are in the official release!
great work guys!!! this is exactly what I've been needing.
Is there a way that a Transform can fail if it detects that it was not the correct match and allow a Transform further down the chain to try? Maybe by raising a specific exception.
If a transform fails then all the others defined before it (from newest to oldest) are tried. If no transform ends up matching then the original argument is returned.
I have a similar but different problem. I have various ways to specify users and various ways to specify people, and various kinds of steps where I use either one (I have many more needs like this as well). I would like to have a each scenario use any combination in a single Given statement. I'd like something like the following:
TokenTransform "PERSON", /^a young child$/ do
…code that creates a young child person and returns it…
end
TokenTransform "PERSON", /^an elderly person$/ do
…code that creates an elderly person and returns it…
end
TokenTransform "USER", /^a guest user$/ do
…code that creates a guest user and returns it…
end
TokenTransform "USER", /^a user with role (w+)$/ do | roleString |
…code that creates a user with a certain role and returns it…
end
Given /^(PERSON) added by (USER)$/ do | person, user |
… (code that uses person and user as their corresponding types, already converted from a string)…
end
Is there a way to do this already? If not, how might something like this look like if it were added to Cucumber?
Heya Eric,
I'm not sure if I understand your situation exactly, let me know if this solves it:
# support file
Transform /^new young child (\\w+)$ do |first_name|
Person.gen(:child, :first_name => first_name)
end
Transform /^new guest user (\\w+)$/ do |username|
User.gen(:guest, :username => username)
end
Transform /^person (\\w+)$/ do |first_name|
Person.first :first_name => first_name
end
Transform /^user (\\w+)$/ do |username|
User.first :username => username
end
# step definition file
Given /^a (\\w+) added by (\\w+)$/ do | person, user |
user.add person
end
Then /^(user \\w+) has a referral for (person \\w+)$/ do |user, person |
user.should have_referral_for(person)
end
# feature file
Given a new young child Mike added by new guest user coolio
Then user coolio has a referral for person Mike
Ok, so from from you're saying, it seems that the regular expression defined for a Transform does not have to appear verbatim in the regular expression of the Given/Then/When. That was not clear from reading the original description. So, that means that every single captured group (anything in parens) is potentially checked against every defined Transform. Is that right?
Yup, the Transform regex just has to match a capture group, not have identical text. That means groups in step defs that don't match anything get checked against every Transform, and a group that matches short circuits checking the rest.
It looks like pickle has the same idea with #{capture_model} and #{capture_fields}, but it doesn't seem to allow us to write our own "token transforms" either.
Cucumber might not have your situation built in, but Transform are very flexible! I think I understand what you need better now, check out this implementation of your TokenTransform:
http://gist.github.com/191691
Cucumber might not have your situation built in, but Transform are very flexible! I think I understand what you need better now, check out this implementation of your TokenTransform:
http://gist.github.com/191691
Cucumber might not have your situation built in, but Transform's are very flexible! I think I understand what you need better now, check out this implementation of your TokenTransform:
http://gist.github.com/191691
Is there a way to transform table data coming in. I'm working on some practice features for Conway's Game of Life (http://github.com/coreyhaines/practice_game_of_li... and I'd like to be able to use a transform to capture the tables.
For more dry fun with cucumber models take a look at Ian White's pickle plugin, which lets you write steps like
Given a user exists with name: "Frank"
And I go to the user's page
Then I should see "name: Frank"
without writing any steps: it fits in with machinist, factorygirl, or just active record.
http://ianwhite.github.com/pickle/
It's an ongoing project, but it works, and I find it very useful in speeding up feature writing.
Love it Larry – extremely useful and informative :) Nice to see this on the Cucumber docs wiki now too.
The rendering of this post is all broken. It would be nice, if you could make it readable again.