How can I use a FlexMock hash matcher to verify part of an array value?
I am trying to verify that my Rails code calls ActiveRecord's all method (all is syntactic sugar for find :all) like this:
records = Record.all
:limit => RECORD_LIMIT, :offset => record_offset,
:select => 'id',
:conditions => [ 'record_type = ? AND content_score >= ?', 'user', min_content_score ],
:order => 'content_score DESC'
The only 开发者_如何转开发part of this code that I care about in this instance is the :conditions param, and I only care about the SQL snippet, not the actual values of the bound variables. I can use a FlexMock hash matcher to assert that (at least) the :conditions param is present like so:
mock.should_receive(:all).with FlexMock.hsh :conditions => []
However, that only matches calls where the value of the :conditions param is the empty array. What I really want is something like this:
mock.should_receive(:all).with FlexMock.hsh [ 'record_type = ? AND content_score >= ?', Object, Object ]
But tragically, as irb reveals, 'user' and Object are not equivalent:
>> '' === Object
Any good ideas? Are nested matchers possible?
Duck-punching this shouldn't be necessary. You can define your own argument matcher quite easily:
class HashKeyMatcher
def initialize( key )
@key = key
end
def ===( other )
other.respond_to?( :keys ) && other.keys.include? @key
end
def inspect() "HashKeyMatcher( #{@key.inspect} )"; end
end
Then you use it like so:
mock.should_receive(:all).
with( HashKeyMatcher.new( :conditions ) ).
and_return []
Place the HashKeyMatcher somewhere that you can access from your tests.
If you like, you can make it available in a manner similar to the other Flexmock argument matchers by opening the Flexmock class and adding a new method:
class Flexmock
def self.key( hash_key )
HashKeyMatcher.new hash_key
end
end
Then you can write your test like this:
mock.should_receive( :all ).
with( Flexmock.key( :conditions ) ).
and_return []
The solution to this requires both monkey-patching FlexMock and an object, so it is not for the faint-of-heart. :)
First, monkey-patch FlexMock::HashMatcher to call == on the expectation object, not the actual object:
class FlexMock::HashMatcher
def ===(target)
@hash.all? { |k, v| v == target[k] }
end
end
Next, construct an object and redefine its == method:
conditions_matcher = Object.new
conditions_matcher.instance_eval do
def ==(other)
other.first == 'record_type = ? AND content_score >= ?'
end
end
Finally, set up your FlexMock expectations:
mock.should_receive(:all).
with(FlexMock.hsh(:conditions => conditions_matcher)).
and_return []
This works for all of the following calls:
Record.all :conditions => [ 'record_type = ? AND content_score >= ?' ]
Record.all :conditions => [ 'record_type = ? AND content_score >= ?', 'foo' ]
Record.all :conditions => [ 'record_type = ? AND content_score >= ?', 'foo', 5.0 ]
Ruby is insane, but in a good way. :)
精彩评论