One thing I really miss when I’m working in a Shoulda-less environment is contexts in my tests. Contexts let me properly arrange my tests in a way that makes sense, clean up my setup methods, and write/generate test names that are easily readable and immediately convey what’s meant to be tested.
Recently I submitted a Rails patch to allow for an :on option in ActiveRecord::Observer. As an example, I wanted to be able to write code like this:
class HappenableObserver < ActiveRecord::Observer
observe :comment, :photo, :on => :after_create
observe :post, :on => [:after_create, :after_update]
observe :event, :participation, :on => [:after_create, :after_update, :after_destroy]
# Actual callback methods omitted....
end
The intent being that the after_create is the only one that gets fired for Comment and Photo, Post gets after_create and after_update, etcetera, the HappenableObserver being a kind of activity feed creator. Right now, without doing it by hand or creating separate observers, this isn’t possible in ActiveRecord::Observer. When writing the patch, I ran into places where I needed an object, and didn’t actually need a different instance of it for every test method. Enter, ghetto-contexts. Here’s an excerpt from my tests for the patch:
class ObserverTest < ActiveRecord::TestCase
def setup
@observer = ActivityObserver.instance
end
lambda do # with topic
topic = Topic.new
test "should observe after_create on Topic" do
@observer.expects(:after_create).with(topic)
topic.send(:notify, :after_create)
end
test "should observe before_destroy on Topic" do
@observer.expects(:before_destroy).with(topic)
topic.send(:notify, :before_destroy)
end
test "should observe before_save on Topic" do
@observer.expects(:before_save).with(topic)
topic.send(:notify, :before_save)
end
test "should ignore after_save and after_validation on topic" do
@observer.expects(:after_save).with(topic).never
@observer.expects(:after_validation).with(topic).never
topic.send(:notify, :after_save)
topic.send(:notify, :after_validation)
end
end.call
end
All the greatness of blocks is often overlooked in Ruby. Since a test method that takes a block was added to ActiveSupport::TestCase, a lot of new possibilities open up. In this case, since the test is defined from a block, I can make an enclosing block, define my object as a local variable there, then define all my test methods to make use of it. I don’t need to instantiate an object that other tests don’t even use in the setup method, and I can group tests in a slightly cleaner way.
This isn’t always an applicable technique - in cases where a new Topic instance was needed every instance, this would fall over. If you need a single instance of an object for your tests, and want to organize them a little more nicely, consider it next time you’re writing a test in a testing environment that lacks actual contexts. Moving your testing to use something like Shoulda or RSpec is most likely a better long-term fix.

Comments
New Comment