Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Rolling your own object creation methods for specs

Lately my favorite way to create objects in my spec suite is to use an object mother pattern. There are a number
of object mother libraries to choose from (see ruby toolbox for a few), but it’s such an easy pattern
to implement that lately I’ve just been rolling my own. In this post I’ll describe what I’ve been using recently and
why I’ve chosen it over using gems.

To implement a basic object mother pattern, all you need to do is define a few methods that are available to your specs, like so:

def new_post(overrides = {})
  Post.new( {:title => "Some title"}.merge(overrides) )
end

def create_post(overrides = {})
  new_post(overrides).tap(&:save!)
end

This allows you to initialize a new, valid Post, or create one with a single method call:

new_post
create_post
create_post(:title => "Some other title")

You might notice that if you use attr_protected for the Post#title attribute, the previous example wouldn’t work.
So the next step is to make sure that protected attributes are assigned correctly:

def new_post(overrides = {})
  Post.new do |post|
    post.title = "Some title"
    overrides.each do |method, value|
      object.send("#{method}=", value)
    end
  end
end

Let’s say you add a unique constraint the Post#title field. You’ll need to be able to generate a unique post name.
Here’s a quick and dirty (and probably non-thread-safe) way to create unique post titles:

def new_post(overrides = {})
  Post.new do |post|
    post.title = "Some title #{counter}"
    overrides.each do |method, value|
      object.send("#{method}=", value)
    end
  end
end

def counter
  @counter ||= 0
  @counter += 1
end

Associations are typically simple to deal with:

def new_comment(overrides = {})
  Comment.new do |post|
    comment.post = new_post
    overrides.each do |method, value|
      object.send("#{method}=", value)
    end
  end
end

This allows you to do the following:

comment = new_comment
comment.save! # => ActiveRecord will automatically save the post for you

comment = new_comment(:post => Post.first)
comment.save! # => will use the post you passed in

Notice that the new_comment method initializes a new post even if you pass in a post.
This might seem like a detail, but initializing the Comment object unnecessarily will increase the time it takes your
spec suite to run. This might be trivial, but in a large test suite it can add up. To see how much of an impact it
might have in your app, you can run some simple benchmarks:

Benchmark.realtime do
  1000.times { Comment.new :post => Post.new }
end

Benchmark.realtime do
  post = Post.new
  1000.times { Comment.new :post => post }
end

In an app that I’m working on now, it showed that initializing a new post took over twice as long as not doing it. With that in
mind, it’s easy to solve that problem:

def new_comment(overrides = {})
  overrides[:post] = proc { new_post } unless overrides.has_key?(:post)
  Comment.new do |comment|
    overrides.each do |method, value_or_proc|
      comment.send("#{method}=", value_or_proc.is_a?(Proc) ? value_or_proc.call : value_or_proc)
    end
  end
end

This way, the Post is only initialized when one isn’t passed in. With those pieces in place, an example file might look like this:

# spec/spec_helper.rb
RSpec.configure do |config|
  config.include ObjectCreationMethods
end

# spec/support/object_creation_methods.rb
module ObjectCreationMethods
  def new_post(overrides = {})
    defaults = {:title => "Some title #{counter}"}
    Post.new { |post| apply(post, defaults, overrides) }
  end

  def create_post(overrides = {})
    new_post(overrides).tap(&:save!)
  end

  def new_comment(overrides = {})
    defaults = {:post => proc { new_post }, :text => "some text"}
    Comment.new { |comment| apply(comment, defaults, overrides) }
  end

  def create_comment(overrides = {})
    new_comment(overrides).tap(&:save!)
  end

  private

  def counter
    @counter ||= 0
    @counter += 1
  end

  def apply(object, defaults, overrides)
    options = defaults.merge(overrides)
    options.each do |method, value_or_proc|
      object.send("#{method}=", value_or_proc.is_a?(Proc) ? value_or_proc.call : value_or_proc)
    end
  end
end

You might be wondering why you might roll your own rather than using an existing library
like Factory Girl or Fixjour. A few of the benefits are:

  • The methods are not generated by meta-programming, so IDEs like RubyMine (or editors that make use of CTags) can offer code completion and refactoring support
  • It’s plain ruby, and is unlikely to break as ActiveRecord updates itself, whereas using a 3rd-party library you can’t upgrade Rails until that 3rd-party library supports the new Rails version
  • When you have complex object graphs it’s easy to initialize or create objects in the exact manner you’d like, as opposed to potentially being constrained by the library you are using
  • Developers on the project don’t have to learn a 3rd-party library’s api or idiosyncrasies
  • It takes about the same time to write these methods as it does to define similar methods in libraries like FactoryGirl
  • There are only 2 plumbing methods to support the framework – it’s super simple to understand

One feature I’ve seen implemented in object mother libraries is support for attribute hashes, similar to:

def valid_comment_attributes(overrides = {})
  {:post => new_post}.merge(overrides)
end

Presumably these attribute hashes would be used for passing into controller specs. In practice, I’ve never seen this work
as expected. In the example above, rspec would happily pass a new Post object into the controller spec, but that could never happen
in real life. However, if you wanted those valid attributes, you could easily incorporate those into your home-rolled ObjectCreationMethods.

I’ve used this pattern on several recent projects ranging from Rails 2.2.2 on Ruby 1.8.6 to Rails 3.1 on Ruby 1.9.2 and
it just works.

Comments
  1. Jim Kingdon says:

    I always like the idea of a gem, to (a) make it easier to switch from one project to another, bring new people up to speed, etc, (b) encapsulate the knowledge about things like the speedup and the attr_protected thing mentioned above. I believe the slogan is “prefer mechanism to idiom” (I heard this from William Pietri, don’t know if it has been published anywhere).

    On the other hand, I also see the point about how these gems seem a bit overengineered for what is a fairly simple task.

  2. Brennan Falkner says:

    def new_comment(overrides = {})
    Comment.new do |post|
    comment.post = proc { new_post }
    overrides.each do |method, value_or_proc|
    object.send(“#{method}=”, value_or_proc.is_a?(Proc) ? value_or_proc.call : value_or_proc)
    end
    end
    end

    Few issues with this. You’re assigning #post to a proc and if there’s any type coercion etc. that’s not going to work, and if they don’t override then it’ll remain a proc. You’re also leaking state. Seeing as this isn’t in the final version, that it’s broken, comment is named post.. I assume it was just for the post anyway. :

    Now you’re accepting procs for values. Okay. But your stated goal was to not call default init if they do pass in a value.

    comment.post = overrides.fetch(:post) { new_post }

    I’m surprised there isn’t a destructive fetch, that’s what you’d really want. Except for maybe this..

    overrides[:post] = overrides.fetch(:post) { new_post }
    Comment.new(defaults.merge(overrides), :without_protection => true)

  3. Bardi Einarsson says:

    The early worm gets the bird,

    This blog post is a Josh Susser, Ruby Rogues pick.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *