Enabling/disabling observers for testing 1
If you use ActiveRecord observers in your application and are concerned about the isolation of your model unit tests, you probably want some way to disable/enable observers. Unfortunately, Rails doesn't provide an easy way to do this. So, here's some code I threw together a while ago to do just that.
module ObserverTestHelperMethods
def observer_instances
ActiveRecord::Base.observers.collect do |observer|
observer_klass = \
if observer.respond_to?(:to_sym)
observer.to_s.camelize.constantize
elsif observer.respond_to?(:instance)
observer
end
observer_klass.instance
end
end
def observed_classes(observer=nil)
observed = Set.new
(observer.nil? ? observer_instances : [observer]).each do |observer|
observed += (observer.send(:observed_classes) + observer.send(:observed_subclasses))
end
observed
end
def observed_classes_and_their_observers
observers_by_observed_class = {}
observer_instances.each do |observer|
observed_classes(observer).each do |observed_class|
observers_by_observed_class[observed_class] ||= Set.new
observers_by_observed_class[observed_class] << observer
end
end
observers_by_observed_class
end
def disable_observers(options={})
except = options[:except]
observed_classes_and_their_observers.each do |observed_class, observers|
observers.each do |observer|
unless observer.class == except
observed_class.delete_observer(observer)
end
end
end
end
def enable_observers(options={})
except = options[:except]
observer_instances.each do |observer|
unless observer.class == except
observed_classes(observer).each do |observed_class|
observer.send :add_observer!, observed_class
end
end
end
end
end
Include this in a Test::Unit::TestCase or 'include' in your RSpec configuration, whatever rocks your boat. Here's a stupid example:
class SomethingCoolTest < Test::Unit::TestCase
include ObserverTestHelperMethods
def setup
disable_observers
end
def teardown
enable_observers
end
def test_without_observers
# ...
end
end
When you go to test the behavior of the observer itself, simply disable/enable like the following to disable/enable all observers except the one you're testing:
class DispassionateObserverTest < Test::Unit::TestCase
include ObserverTestHelperMethods
def setup
disable_observers :except => DispassionateObserver
end
def teardown
enable_observers :except => DispassionateObserver
end
def test_without_observers_except_dispassionate_observer
# ...
end
end


I've done some testing around observers recently and we came up with a simple way of preventing observers from interfering with our tests - it's not as flexible as your solution but I'm not sure it needs to be that flexible.
Given a model Foo, and an observer FooObserver, in our Foo tests, in the setup, we would stub the Foo#notify method (we are using mocha):
We would then test our Observers separately:
This allows us to test our objects and their observers in isolation; in general you can rely on Rails to do the right thing and make sure your observers get called in the right place although you could also write some integration/functional tests to confirm this.