Leveraging Test Data Builders in Cucumber Steps

Finding the balance between writing declarative scenarios and step reuse in Cucumber can be tricky at times. I have found leveraging a Test Data Builder library (often selling itself as an Object Mother library) helps in step reuse and prevents your scenarios from being overloaded with a lot of noise. There are many test data builder libraries out there to do this. Some of the popular ones are: Fixjour, FixtureReplacement, and FactoryGirl. (There are tons more on github.)

In the context of Cucumber these test builders work well when you need to create some records with default values in Given steps. Combined with the power of Cucumber step tables you can further leverage the builder pattern by providing the pertinent values to just the attributes that matter in each scenario. The code below works out of the box for builders that adhere to the “create_model_name” API, but can be easily adapted to any API. I have some thoughts on how to further leverage these builders in Cucumber along with Webrat but that is for another post. In the mean time, enjoy and fork away!

# step_definitions/builder_steps.rb
module BuilderStepHelpers

  def create_model(model_name, attributes={})
    send("create_#{model_name.gsub(' ','_')}",attributes)
  end

end
World do |world|
  world.extend BuilderStepHelpers
end

# Examples:
# Given the following widget exists:
# | Name  | Price |
# | Foo   | 20.00 |
# Given the following pets exist:
# |  Pet Type | Months Old     |
# | Dog       | 23             |
# | Cat       | 34             |
Given /^the following (.+?)(?:s|) exist(?:s|):$/ do |model_name, table|
  table.hashes.each do |hash|
    attributes = {}
    hash.each { |k, v| attributes[k.gsub(' ','').underscore] = v }
    create_model(model_name, attributes)
  end
end

# Example:
# Given widgets named 'Foo', 'Bar', and 'Car' exist
Given /^(.+?)(?:s|) named (.+) exist$/ do |model_name, names|
  names.extract_list.each do |name|
    create_model(model_name, {:name => name})
  end
end

# Example:
# Given an expensive widget exists  (assumes you have a create_expensive_widget method)
# Given a widget exists
# Given 3 widgets exist
# Given 33 widgets exist
#
# Warning: this one can be a little too greedy at times so YMMV from project to project.
Given /^(a|an|\d+) (.+?)(?:s|) exist(?:s|)$/ do |ammount, model_name|
  how_many = ammount =~ /a|an/ ? 1 : ammount.to_i
  1.upto(how_many) { create_model(model_name) }
end
# support/string.rb
# Examples:
# "'Foo', 'Bar', and 'Jar'".extract_list # => ["Foo", "Bar", "Jar"]
# '"Dog", "Cat"'.extract_list # => ["Dog", "Cat"]
class String
  def extract_list
    self.scan((/['"](.*?)["']/)).flatten
  end
end

About this entry

I'm speaking at The Ruby Hoedown 2009!
I'm speaking at MountainWest RubyConf 2009!