Techioz Blog

ActiveRecord オブジェクトとのスタブ ActiveRecord::Relation

概要

Rails アプリをテストしているわけではありません。それを邪魔にならないようにするだけです。

タイムスタンプによってレコードを制限し、比較的アクティブなサーバーに接続するライブラリをテストしています。これらの返されたレコードは時間の経過とともに変化するため、他の制限のテストがより複雑になります。必要な条件を満たすために作成したオブジェクトとの独自のカスタム関係を返すには、ActiveRecord::where メソッドをスタブアウトする必要があります。

何かのようなもの

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

それが私が望むことですが、それはうまくいきません。コード内のオブジェクトに対して ActiveRecord::where と ActiveRecord::select を呼び出すことができる必要があるため、これを ActiveRecord::Relation にする必要があります。

編集 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 ]>

解決策

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 の新しいインスタンスが返されます。