如何使用FlexMock哈希匹配器验证部分数组值?

时间:2011-01-10 14:48:57

标签: ruby-on-rails ruby unit-testing mocking

我正在尝试验证我的Rails代码调用ActiveRecord的all方法(所有方法都是find:all的语法糖),如下所示:

      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'

我在这个实例中关心的这段代码的唯一部分是:conditions param,我只关心SQL片段,而不是绑定变量的实际值。我可以使用FlexMock哈希匹配器断言(至少):条件param存在如下:

mock.should_receive(:all).with FlexMock.hsh :conditions => []

但是,这只匹配其中:conditions param的值为空数组的调用。我真正想要的是这样的:

mock.should_receive(:all).with FlexMock.hsh [ 'record_type = ? AND content_score >= ?', Object, Object ]

但悲惨的是,正如irb所揭示的那样,'用户'和对象并不等同:

>> '' === Object

有什么好主意吗?嵌套匹配器是否可能?

2 个答案:

答案 0 :(得分:1)

鸭子打孔这不应该是必要的。您可以非常轻松地定义自己的参数匹配器:

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

然后你就这样使用它:

mock.should_receive(:all).
  with( HashKeyMatcher.new( :conditions ) ).
  and_return []

将HashKeyMatcher放在您可以从测试中访问的位置。

如果您愿意,可以通过打开Flexmock类并添加新方法,以类似于其他Flexmock参数匹配器的方式使其可用:

class Flexmock
  def self.key( hash_key )
    HashKeyMatcher.new hash_key
  end
end

然后你可以像这样编写你的测试:

mock.should_receive( :all ).
  with( Flexmock.key( :conditions ) ).
  and_return []

答案 1 :(得分:0)

对此的解决方案需要对FlexMock和对象进行猴子修补,因此不适合胆小的人。 :)

首先,猴子补丁FlexMock :: HashMatcher在期望对象上调用==,而不是实际对象:

class FlexMock::HashMatcher
  def ===(target)
    @hash.all? { |k, v| v == target[k] }
  end
end

接下来,构造一个对象并重新定义其==方法:

conditions_matcher = Object.new
conditions_matcher.instance_eval do
  def ==(other)
    other.first == 'record_type = ? AND content_score >= ?'
  end
end

最后,设置您的FlexMock期望:

mock.should_receive(:all).
    with(FlexMock.hsh(:conditions => conditions_matcher)).
    and_return []

这适用于以下所有电话:

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很疯狂,但是很好。 :)