Stub ActiveRecord::与 ActiveRecord 对象的关系

问题描述 投票:0回答:2

我不是在测试 Rails 应用程序。只是把它解决掉。

我正在测试一个连接到相对活跃的服务器的库,通过时间戳限制记录。这些返回的记录会随着时间的推移而变化,使得测试其他限制变得更加复杂。我需要删除

ActiveRecord::where
方法来返回我自己的与我创建的对象的自定义关系,以满足我需要的标准。

类似的东西

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

是我想要的,但这不起作用。我需要它是一个

ActiveRecord::Relation
,因为我需要能够在代码中的对象上调用
ActiveRecord::where
ActiveRecord::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

在规范/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 ]>
ruby activerecord rspec mocking relation
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"]

此外,如果您随后在

where
上调用
relation
,您将返回 Relation
new
实例,该实例将不再被存根。


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
© www.soinside.com 2019 - 2024. All rights reserved.