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.