Let’s say that we have a Ruby object with some class methods. In my case, we want to be able to inject a driver at the class level that all instances will use:
class Mod::Thingy def self.driver end def self.driver=(d) end end
And let’s say that .driver has a default value. So the class looks more like this:
class Mod::Thingy
def self.driver
@driver || DefaultDriver
end
def self.driver=(d)
@driver = d
end
end
Now here’s the dealio. Let’s say that we test for the default value first. So we check Mod::Thingy.driver == DefaultDriver. Then we test the setter. We do Mod::Thingy.driver = OtherDriver, and then check the return value of Mod::Thingy.driver. Fair enough.
But what if we ran the examples in reverse? Now we do Mod::Thingy.driver = OtherDriver first, check it; but now in our second test, we can’t test the default value, because the state of Mod::Thingy has been changed.
Strategies:
-
We might use RSpec’s
aroundfeature to save the state ofMod::Thingy.driverand restore it afterwards. What I don’t like about this is that now the around method has to know all about all of the setters at the class level. Meanwhile, it is possible to imagine objects that are difficult to set back to their default state: Perhaps the class methods have order dependencies themselves. This would make the test need to know a lot about the inner state of the object. -
We might provide a
.reset!method on the object. But this seems a bit nutty: Add a method just to make testing easier? -
What about this in an RSpec
before(:each)block:Mod.send(:remove_const, 'Thingy') load "#{PROJECT_ROOT}/lib/mod/thingy.rb"Now we’re getting a nice clean class. To me, (3) is more parallel to the testing convention of new’ing up an object instance for the test.
Thoughts?

{ 2 comments… read them below or add one }
I may not be grokking all the details here, but I’d lean away from using a class method to define the driver. Instead, I’d use constructor dependency injection, passing an instance of the desired driver to the constructor, with a sensible default.
class Mod::Thingy
attr_reader :driver
def initialize(driver=DefaultDriver.new)
@driver = driver
end
end
Then inject an instance of OtherDriver (or a mock) in the tests. This also happens to be thread-safe — no race conditions, so no synchronization needed.
After reviewing a lot of options, the best strategy seems to be:
(a) inject the driver via a class method. Why? Because then you can do Mod::Thingy.new and get it (and all Mod::Thingys) instantiated with the “right” driver.
(b) Run an after handler to reset the class.
It would take awhile for me to outline the reasons, but briefly: Driver injection at the “instance” level is definitely an option, but doesn’t fit very well with the idea that you should be able to do Mod::Thingy.new whenever you want. Injection into an instance implies a factory, which would be fine in Java but seems like overkill in Ruby . . .