开发者

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. :)

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜