Dr Nic

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:

  1. Create features/support/form_submission_helpers.rb (its automatically loaded)
  2. Wrap the helper method in a module module FormSubmissionHelpers ... end
  3. 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.

Related posts:

  1. hash bang cucumber I don’t know if this is a good idea or...
  2. Testing outbound emails with Cucumber My testimonial for Cucumber still stands even in 2009....
  3. newgem 1.0.0 all thanks to Cucumber The New Gem Generator (newgem) was exciting, moderately revolutionary, and...
  4. My .irbrc for console/irb The relatively unspoken warhorse of Ruby/Rails programming is the irb/console...
  5. Extend Prototype $() yourself If you’re using the prototype javascript library, its fun to...

15 Responses to “Cucumber: building a better World (object)”

  1. [...] April 15, 2009 Source: http://drnicwilliams.com/2009/04/15/cucumber-building-a-better-world-object/ [...]

  2. Mischa says:

    Thanks!

  3. AMos King says:

    Why not use the step within a step methods?

    When /I fill in the Account form/ do
      When "I fill in 'account_name' with 'Mocra'"
      When "I fill in 'account_abn' with '12 345 678 901'"
      When "I click the 'Submit' button"
    end
    
  4. Joe Ferris says:

    Why did you want to pull those lines into a separate method? Did “I fill in the Account form” not clearly communicate the intent of the step? Did you want to use that helper from other steps? If so, why not just call the step directly?

    That’s a good explanation of the world object, though.

  5. I’ve been using the Page Object pattern (http://code.google.com/p/webdriver/wiki/PageObjects) when I’m writing my tests with selenium core + junit.

    I think its a good way to avoid duplication and make the code better.

  6. Mark Wilden says:

    0.3.0 doesn’t seem to be backward-compatible. In particular, the email_spec gem you wrote about last time won’t work with 0.3.0, right? In other words, is it an error to use the old style or is it a deprecation warning?

  7. JeffMo says:

    Perhaps you meant “bonnified” as a joke, but it’s a frequent mistake, so I thought I’d mention it.

  8. Dr Nic says:

    @Mark – the email_spec needs to be upgraded yes; it was Ben’s idiom for defining helpers that I’ve been using and which subsequently broke with 0.3.

    @every-questioning-the-refactoring – sorry that wasn’t really the point of the article. It was a trite example with correspondingly trite refactoring. Sorry about that. I was just showing how I’ve seen how/where to refactor your methods to – the support folder inside a module.

  9. Ben Mabey says:

    Email spec works with 0.3.0 now. I had a cucumber-master branch with the changes that I was waiting on merging in until the World changes were on rubyforge.

  10. Ben Mabey says:

    @Amos King
    You could use the steps within steps, but IMO that just seems silly in this case. Webrat provides such a nice API, why not just use it? Using the steps will also be slower than calling the method directly. The advantage of helper methods is that you can also pass in other ruby objects. I like using steps within steps at times but in general I find that using ruby as the common denominator easier to work with.

  11. Great explanation of how World works Nic. Now I know where to send people who upgraded to 0.3.0 and “forgot” to read the release notes :-)

  12. Dr Nic says:

    @Aslak, we’re very excited that the sample step definitions (when you run cucumber and it finds a step for which there are no definitions) now inserts regular exception groups within “…” examples. That was very cool to find.

  13. Glad you like it! It’s just a little smiley feature ;-)

  14. Jason F says:

    Minor item: in cucumber 0.3.2 the webrat_steps.rb file has a slightly different syntax for fill_in. Change:

    fill_in(“account_name”, “Mocra”)

    to

    fill_in(“account_name”, :with => “Mocra”)

    Then it works. Thanks for the good post doc.

  15. Dr Nic says:

    @JasonF – thx for the reminder; I’ve updated the examples above.