Object Mother…in rails?

That’s right, the circa 2000 pattern still makes sense today. Use an “object mother” as a test factory to conveniently create objects for your unit tests to bang against. It will often default values, or have different states in which to create objects. For example, you might have a new_user, as well as a new_superuser and new_guest method all of which return users.

Read about the original pattern

But why, you ask, not just use rails’ fixtures? Glad you asked.

  1. it’s more intuitive and maintainable to setup the data you need right next to the test
  2. it’s easier to create just exactly the objects you need when you have test factory methods

But don’t take my word for it. Let’s look at some code.

First off, what does this look like in ruby? I am creating an “ObjectMother” module, which I then just mixin to my tests. A test might then look like :

  include ObjectMother
 
  def test_delete_project
    project = new_project('foo')
    post :delete, :id => project.id
    assert_raise(ActiveRecord::RecordNotFound) { Tag.find(project.id)}
  end

The magic is in “new_project”. It’s an entirely pragmatic construct. If you pass it a string, it will set everything else to acceptable defaults, and use that string as a name. It looks something like this.

module ObjectMother
  def new_project(options)
    options = {:name => options} if options.is_a? String
    options[:url_name] = options[:name].gsub(/\W/, '') if !options.has_key?(:url_name)
    Project.create!(options)
  end
 
  ...
end

Here in project, it defaults the url_name (which must be unique) from the name you’ve given it. However, you could also create a more custom project by running this :

  new_project(:name => 'garage', :url_name => 'the_garage', :description => 'foo')

This is how it works for projects, but it’s only purpose in life is to make my life easier and reduce the amount of code one has to type or read. So each type of thing it creates works a little bit differently according to our needs
——
h2. A more complicated example

I was playing with ferret last week, and wrote a test that looked like this :

  def test_across_types
    project = new_project('rabbit holes')
    post = new_post(:subject => 'a rabbit has a big head')
    user = new_user(:display_name => 'rabbit head')
 
    @search.string = 'rabbit'
    assert_find [project, post, user]
 
    @search.string = 'rabbit head'
    assert_find [post, user]
  end

My thought process was something along the lines of :

  1. I want to test that my searcher works across types
  2. I need to create a project, post, and user that all have a term in them (in different places)
  3. I want to search for the term, and make sure i get all of them
  4. I want to search for a term that maybe 2 of them have and make sure I only get those 2

Writing the test for this part literally took 30 seconds, I didn’t have to go lookup the fixtures or add a new fixture for my new case. I also didn’t have to remember all the things that it takes to make a valid project or post or user.

Tags: , ,

Comments are closed.