I recently found squirrel, and I wanted to use it for a project we’re working on to simplify some complex finder statements. Squirrel allows turning something like this:
Task.find(:all, :conditions => [ 'active = ? and (updated_at > cache_version or cache_version IS NULL)', true ] )
into:
Task.find(:all) do active == true any do updated_at > cache_version cache_version.nil? end end
The problem is testing
Then I ran into a serious problem – how to test this piece of code using rspec? Here was my first attempt:
it 'should find all active tasks where updated_at is greater than cache_version or cache_version is null' do Task.should_receive(:find).with(:all).and_return(@tasks) Task.update_cache end
This doesn’t test the search conditions at all. So I moved on to yield:
Task.should_receive(:find).with(:all).and_yield received unexpected message "active"
Then I started adding in the expectations:
Task.should_receive(:active) Task.should_receive(:find).with(:all).and_yield
But how do I know that active is being compared to true? Now I’d have to use a mock to do that:
mock_active = mock(:active) mock_active.should_receive(:==).with(true) Task.should_receive(:active).and_return(mock_active) Task.should_receive(:find).with(:all).and_yield
As you can see, this is getting quite painful. So it was time to abstract this out into something more meaningful. I created a class called FindWithSquirrel:
class FindWithSquirrel include Spec::Matchers def initialize(klass, expected) @klass = klass @expected = expected end def verify end end
And I added a way to gain access to the class:
class Class def should_receive_squirrel_find_with(expected) FindWithSquirrel.new(self, expected) end end
Now the new class needs to extend ActiveRecord to override find and record what happens:
class FindWithSquirrel def initialize(klass, expected) ... extend end def extend @klass.class_eval %Q{ def self.find_with_finds @find_with_finds ||= [] end def self.find_with_find_with(*args, &blk) find_with_finds << find_without_find_with(:query, &blk).to_find_conditions find_without_find_with(*args, &blk) end class << self alias_method :find_without_find_with, :find alias_method :find, :find_with_find_with end } end end
So now I’ve got a variable on the model class holding an array of generated conditions. I can fill in the verify method:
class FindWithSquirrel def finds @klass.find_with_finds end def verify finds.should include(@expected) end end
That’s all fine, but now I need to make rspec actually call my verify method. I can do that by reusing the way rspec mocks work. I can do that by adding my class instances to the same array that rspec adds it’s mock expectations:
class FindWithSquirrel def initialize(klass, expected) ... $rspec_mocks.add(self) unless $rspec_mocks.nil? end end
Now the rspec mock framework is expecting to call the methods ‘rspec_verify’ and ‘rspec_reset’, so:
class FindWithSquirrel alias_method :rspec_verify :verify def rspec_reset end end
Now I can run this spec, and it works great. But it seems to break all the subsequent specs that also use the find function. I have to flesh out that reset function a little. Remember my earlier extend function – I need to remove my ActiveRecord extensions:
class FindWithSquirrel def unextend @klass.class_eval %Q{ class << self alias_method :find, :find_without_find_with end } end alias_method :rspec_reset, :unextend end
So what does my spec look like?
it 'should find all active tasks where updated_at is greater than cache_version or cache_version is null' do Task.should_receive_squirrel_find_with( ["(tasks.active = ? AND (tasks.updated_at > tasks.cache_version OR tasks.cache_version IS NULL))", true] ) Task.update_cache end
So the class is testing that squirrel is outputting the correct conditions. You could argue that squirrel’s own tests fulfill this testing need, but this class gives a good jumping point to testing that squirrel is receiving the correct parameters. The full file can be found here. Just include it from your spec_helper.

Is there a reason you decided against using named_scope for “active = true” aspect of the query? You could then “chain the finders” to constrain your results more.
The “updated_at” or semantics do look like a valid use case for squirrel though. Of course, it is 5pm on a Friday when I’m saying all this, so I may be way off base.
Walter McGinnis´s last blog – Have fun testing your application with Cucumber
@walter: Hi, actually the above example is contrived just to show how squirrel does AND and OR. I’m using squirrel for slightly more complex single queries.