使用ActiveRecord对象填充桩 ActiveRecord::Relation

11

我并不是在测试Rails应用程序。这只是我的交代。

我正在测试一个连接到相对活跃的服务器并通过时间戳限制记录的库。随着时间的推移,返回的记录会发生变化,从而使测试其他限制更加复杂。我需要对ActiveRecord::where方法进行桩处理,以返回我自己的自定义关系,并使用我创建的对象来满足我所需的条件。

像这样:

relation = double(ActiveRecord::Relation)
relation.stub(:[]).and_return( [MyClass.new(...), MyClass.new(...), ...] )
MyClass.stub(:where).and_return( relation )

这是我想要的,但不起作用。我需要它成为一个ActiveRecord::Relation,因为我需要在代码中调用ActiveRecord::whereActiveRecord::select


编辑 2014-01-28

在lib/call.rb中

class Call < ActiveRecord::Base
  class << self
    def sales start_time, end_time
      restricted_records = records(start_time, end_time, :agent_id)
      #other code
    end

    #other methods

    private

      def records start_time, end_time, *select
        # I'm leaving in commented code so you can see why I want the ActiveRecord::Relation object, not an Array
        calls = Call.where("ts BETWEEN '#{start_time}' AND '#{end_time}'") #.select(select)
        raise calls.inspect
          #.to_a.map(&:serializable_hash).map {|record| symbolize(record)}
      end
  end
end

在 spec/call_spec.rb 文件中

require 'spec_helper'
require 'call.rb'

describe Call do
  let(:period_start) { Time.now - 60 }
  let(:period_end) { Time.now }

  describe "::sales" do
    before do
      relation = Call.all
      relation.stub(:[]).and_return( [Call.new(queue: "12345")] )
      Call.stub(:where).and_return( relation )
    end

    subject { Call.sales(period_start, period_end) }

    it "restricts results to my custom object" do
      subject
    end
  end
end

测试输出结果:

RuntimeError:
  #<ActiveRecord::Relation [ #an array containing all the actual Call records, not my object ]>
2个回答

5

ActiveRecord::Relation 是一个类,:[] 是该类的一个实例方法。你正在学习如何存根该类本身的方法,所以它不会被 Rails 代码调用。

如果你想让 MyClass.where 返回仅存根了 :[] 的关系,则需要先创建一个 Relation 实例,例如:

relation = MyClass.all
relation.stub(:[]).and_return( [MyClass.new(...), MyClass.new(...), ...] )
MyClass.stub(:where).and_return( relation )

然而请注意,在此上下文中要获取返回数组,你需要执行以下操作:
MyClass.where("ignored parameters")["ignored parameters"]

此外,如果您随后在relation上调用where,将返回一个新的Relation实例,该实例将不再被存根。

и°ўи°ўжӮЁзҡ„еӣһеӨҚпјҢдҪҶиҝҷеҸӘжҳҜиҝ”еӣһдәҶеҲқе§Ӣзҡ„MyClass.allе…ізі»иҖҢдёҚжҳҜжҲ‘зҡ„еӯҳж №е“Қеә”гҖӮжҲ‘еә”иҜҘеӯҳж №дёҚеҗҢзҡ„ж–№жі•еҗ—пјҹ - Brad Rice
еҪ“дҪ иҜҙвҖңthisвҖқеҸӘиҝ”еӣһеҲқе§Ӣзҡ„MyClass.allж—¶пјҢдҪ жҢҮзҡ„жҳҜд»Җд№Ҳпјҹ - Peter Alfvin
嗯,这不是代码在我的电脑上的表现。我把这些行复制到我的编辑器中,更改了类名并运行了测试。它返回了所有记录,而不是我的受限集合。 - Brad Rice
好的,你没有在where结果上调用[]方法,所以你的存根没有生效。如果你在你的规范中说subject['whatever'],你会得到自定义数组。如果你想让where本身返回自定义数组,你可以这样做,但那将不是一个关系 - 它将是一个数组。 - Peter Alfvin
好的。我主要是想知道这是否可能,但根据你刚才的说法,似乎不行。看起来我还是得硬着头皮创建一个测试数据库。感谢你的帮助。 - Brad Rice
显示剩余3条评论

3

2022年更新

之前被点赞的答案完全不正确,因为它不能与索引、.to_a.first.last.any?.none?以及几乎所有其他方法一起使用。

相反,您可以通过存根其 records 方法来模拟关系中包含的记录。

custom_records = ["a", "b", "c"]

relation = Model.all
relation.stub(:records).and_return(custom_records)

allow(Model).to receive(:where).and_return(relation)

# Later ...

records = Model.where('1 + 1 = 2') # content of the query doesn't matter, .where is mocked
records.first # => "a"
records.last # => "c"
records.to_a # => ["a", "b", "c"]
records.any? { |x| x == "b" } # => true

大多数方法都可以工作,但有一些例外需要单独存根。

  • .count - 直接调用 SELECT COUNT(*) SQL 查询,绕过了我们的 records 模拟。修复方法:
    relation.stub(:count).and_return(custom_records.count)
    
  • .exists? - 再次直接调用另一个 SQL 查询,绕过了我们的 records 模拟。修复方法:
    relation.stub(:exists?).and_return(custom_records.present?)
    
  • 其他 - 可能还有其他方法需要进行模拟(取决于您的代码是否使用这些方法),您可以根据需要对每个方法进行模拟。
此外,您可以通过以下方式模拟 has_many 关系的返回值(这是我在搜索此问题时的实际用例):
allow(record).to receive(:related_records).and_wrap_original do |original, *args, &block|
  relation = original.call(*args, &block)
  relation.stub(:records).and_return(my_custom_array_of_related_records)
  relation
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接