Pular para o conteúdo
abril 16, 2009 / cassiomarques

A Cucumber step to generate any number of objects, for any model

A simple approach using Factory-Girl and some Ruby to prepare Cucumber scenarios with any number of objects, for any model

The Problem

When we write Cucumber scenarios, many times we’ll have to previously generate a certain number of records in the database. Then it becomes necessary to create several steps, each one capable to create objects for a specific model. It can be become very hard if we have many different models.

Given there are 3 products

Here we have ‘products’, which probably means we have a Product model and a step similar to this:

Given /^there are (\d+) products$/ do |n|
  n.to_i.times { Product.create(:description => 'foo', :price => 15.00) }
end

Now imagine writing this kind of code for several models. Also, imagine the hell it will be if you have several validates_uniqueness_of statements in these models…

The Solution

We need to create a generic way to create objects for our scenarios. With some help from Factory-Girl and some Ruby code, it can become very easy!

1 – Configuring Cucumber to use Factory-Girl

I’ve explained it here before, it’s simples!

  • Create a new folder to group all your factories. I use the same factories both for Cucumber and Rspec, so I put everything inside spec/factories. You can put it anywhere that makes sense for your project.
  • Inside this folder, create a file for each factory. It helps to keep everything organized. By convention, the name of each factory should be the name of the model, in singular, with camelCase replaced by underlines. For example, the definition of a factory for a ProductCategory model, inside a spec/factories/product_category_factory.rb file would look something like this

        Factory.define(:product_category) do |pc|
          # ...
        end
        
  • Configure Cucumber to include the factories when the scenarios are executed. We just need to add two lines of code in the features/support/env.rb file:

        require 'factory_girl'
        Dir.glob(File.join(File.dirname(__FILE__), '../../spec/factories/*.rb')).each {|f| require f }
        

2 -Creating our magic step definition

The idea is to always write our steps with the pluralized name of the model, with camelCase replaced by spaces. This helps to make everything more readable e takes advantage of the convention we defined for the factories’ names to easily the right factory to use to build our objects. Another detail is that if we specify that in our step there should be “x” objects for a certain model, then all the objects currently in the database must be deleted. Let’s see some code! This code can be defined in any file inside features/step_definitions:

Given /^there are (\d+) (.+)$/ do |n, model_str|
  model_str = model_str.gsub(/\s/, '_').singularize
  model_sym = model_str.to_sym
  klass = eval(model_str.camelize)
  klass.transaction do
    klass.destroy_all
    n.to_i.times do |i|
      Factory(model_sym)
    end
  end
  end

This code does the following:

  • Create any number of objects for any model.
  • Opens a transaction and delete all objects for this model from the database.
  • Creates n objects using Factory Girl (assumes there is a factory defined with a symbol which is the name of the model used in the scenario, with spaces replaced by underline and singular)

Now we can write steps like

Given there are 123 clients

or

Given there are 123 clients
And there are 7 product categories

and never worry with the definition for these steps again.

6 Comentários

Deixe um comentário
  1. Carl Youngblood / ago 15 2009 8:01 pm

    Good job Cassio! Just one recommendation: instead of using eval, I recommend using:

    klass = model_str.camelize.constantize

    I’ve heard it’s quite a bit faster and that one should generally avoid using eval.

    • cassiomarques / ago 15 2009 8:46 pm

      Thanks Carl!

      Yes, your’re right, constantize will also make the code cleaner!

  2. Henning Koch / set 26 2009 1:11 pm

    We recently published a Gem cucumber_factory that follows a similiar approach and also lets you set attributes and associations:

    http://github.com/makandra/cucumber_factory

  3. Amol Kelkar / jun 9 2010 10:26 pm

    Awesome post! Thank you.

  4. neohomerico / dez 14 2010 9:44 pm

    Thanks for this post, very useful

Deixe um comentário