Metaprogramming Unit Tests

by Sean Cribbs

Here's how a lot of my tests look right after I've created my model. I got this pattern from Rick Olson's restful_authentication generator plugin.

class UserTest < Test::Unit:TestCase
  creator :first_name => "Joe", :last_name => "Schmo", :email => "joe.schmo@example.com"
  params  :first_name,          :last_name,            :email,                           :date_of_birth
end

It's a nice clear pattern, but I inevitably make typing errors, especially in the creation method. This problem is exacerbated when I'm writing essentially the same test for tons of different models. Here's how I'd love it to look:

require File.dirname(__FILE__) + '/../test_helper'

class EventTest < Test::Unit::TestCase
  fixtures :events  
  creator :name => "Test Event", :date => 1.week.from_now
  default_creation_test
end

All that's needed now is a little reflection and define_method magic. Here's what I coded into my test_helper.rb (inside the Test::Unit::TestCase class), with some help from ReinH and eventualbuddha in #rubyonrails.

  def self.creator(options={})   
    klass = self.name.chomp("Test")
    define_method("create_#{klass.underscore}") {|*args| 
    opts = args.first || {}
    klass.constantize.create(options.merge(opts)) }
  end

  def self.default_creation_test
    klass = self.name.chomp("Test")
    define_method("test_should_create_#{klass.underscore}") {
      assert_difference klass.constantize, :count do
        object = send("create_#{klass.underscore}")
        assert !object.new_record?, "#{object.errors.full_messages.to_sentence}"
      end
    } 
  end

What I love about this is that it is both a prime example for extraction into a pattern, and that it serves my desire to be a lazy programmer! Kudos again for Ruby. I also added in another method for testing creation validations, which I'll let you investigate in this pastie.

© 2006-present Sean CribbsGithub PagesTufte CSS