- loading...
Cucumber: building a better World (object)
How to write helper libraries for your Cucumber step definitions and how to upgrade your support libraries from Cucumber 0.2 to 0.3 (released today).
In cucumber, each scenario step in a .feature file matches to a Given, When, Then step definition. The step definitions are normal Ruby code. First class, bonnified, honky-tonk Ruby code. And what’s the one thing we love to do to Ruby code on a rainy Sunday afternoon? Refactor it. Turn messy code into readable “return in 50 years, on the time capsule, and get back to work quickly” code.
In Cucumber we use a special, until-now unknown, magic technique for refactoring step definitions. They are called “Ruby methods”. Oooh, feel the magic. You take some code in a step definition and you refactor it into a method. And you’re done. For example:
When /I fill in the Account form/ do
fill_in("account_name", :with => "Mocra")
fill_in("account_abn", :with => "12 345 678 901")
click_button("Submit")
end
Could be refactored into:
When /I fill in the Account form/ do
fill_in_account_form
end
def fill_in_account_form
fill_in("account_name", :with => "Mocra")
fill_in("account_abn", :with => "12 345 678 901")
click_button("Submit")
end
Good work. Or is it? No, we’ve done something a little naughty. We’ve polluted the global object space with our method and turns out it just isn’t necessary. There’s a nicer way and a clean idiom for how/where to write helper methods.
Annoyingly, that idiom broke with the release of Cucumber 0.3. So I’ll introduce both so you can fix any bugs that you spot and know how to fix them.
The solution is to understand the existence of the World object and the clean technique for writing features/support/foobar_helpers.rb libraries of helper methods.
Introducing the World object
To ensure that each cucumber scenario starts with a clean slate, your scenarios are run upon a blank Object.new object. Or in a Rails project its a new Rails test session ActionController::IntegrationTest.
These are called World objects (see cucumber wiki). You pass in a specific World object you’d like to use, else it defaults to Object.new For a Rails project, you’re using Cucumber::Rails::World.new for your world object each time, which is a subclass of ActionController::IntegrationTest.
The benefit of a World object starting point for each scenario is that you can add methods to it, that won’t affect the rest of the Ruby world you live in: which will be the Cucumber runner. That is, you cannot accidently blow up Cucumber.
Extending the World object
Step 1, put methods in a module. Step 2, add the module to your World object.
So that we’re all extending Cucumber in the same way, there is a folder for where your helper methods should be stored, and a programming idiomatic way to do it. It has changed slight from Cucumber 0.2 to 0.3 so I’ll show both.
For our helper method fill_in_account_form above:
- Create features/support/form_submission_helpers.rb (its automatically loaded)
- Wrap the helper method in a module
module FormSubmissionHelpers ... end - Tell Cucumber to include the module into each World object for each Scenario
In Cucumber 0.3+ your complete helper file would look like:
module FormSubmissionHelpers
def fill_in_account_form
fill_in("account_name", :with => "Mocra")
fill_in("account_abn", :with => "12 345 678 901")
click_button("Submit")
end
end
World(FormSubmissionHelpers)
For Cucumber 0.2 your complete helper file might have looked like:
module FormSubmissionHelpers
def fill_in_account_form
fill_in("account_name", :with => "Mocra")
fill_in("account_abn", :with => "12 345 678 901")
click_button("Submit")
end
end
World do |world|
world.extend FormSubmissionHelpers
end
Where the difference is the last part of the file. This mechanism is deprecated and results in the following error message after upgrading to Cucumber 0.3:
/Library/Ruby/Gems/1.8/gems/cucumber-0.3.0/bin/../lib/cucumber/step_mother.rb:189:in `World': You can only pass a proc to #World once, but it's happening (Cucumber::MultipleWorld) in 2 places: vendor/plugins/cucumber/lib/cucumber/rails/world.rb:72:in `World' vendor/plugins/email-spec/lib/email_spec/cucumber.rb:18:in `World'
Summary
Refactor step definitions. Put it in features/support/…_helpers.rb files, inside modules that are assigned to the World object.
And a word from our sponsor
Starting a new Rails project and need the team that is up-to-date with all the latest and greatest gadgetry, plugins and gems, styles and processes for enterprise and web2.0 web applications? Ask us at Mocra.
Need professionals to help your Rails project burst over the finish line? Ask us at Mocra.

