16

サンプル間で rspec-mocks の double を共有する方法について質問があります。rspec-mocks を使用して新しい Rails アプリを作成してい3.1.3ます。私は古い (< 2.14) を使用することに慣れており、現在の rspec の使用法について知識を更新しようとしています。

私はモデルメソッドを持っています:

def self.from_strava(activity_id, race_id, user)
  @client ||= Strava::Api::V3::Client.new(access_token: 'abc123')

  activity = @client.retrieve_an_activity(activity_id)

  result_details = {race_id: race_id, user: user}
  result_details[:duration] = activity['moving_time']
  result_details[:date] = Date.parse(activity['start_date'])
  result_details[:comment] = activity['description']
  result_details[:strava_url] = "http://www.strava.com/activities/#{activity_id}"


  Result.create!(result_details)
end

そして、ここに仕様があります:

describe ".from_strava" do
  let(:user) { FactoryGirl.build(:user) }
  let(:client) { double(:client) }
  let(:json_response) { JSON.parse(File.read('spec/support/strava_response.json')) }

  before(:each) do
    allow(Strava::Api::V3::Client).to receive(:new) { client }
    allow(client).to receive(:retrieve_an_activity) { json_response }
    allow(Result).to receive(:create!)
  end

  it "sets the duration" do
    expect(Result).to receive(:create!).with(hash_including(duration: 3635))
    Result.from_strava('123', 456, user)
  end

  it "sets the date" do
    expect(Result).to receive(:create!).with(hash_including(date: Date.parse("2014-11-14")))
    Result.from_strava('123', 456, user)
  end
end

単一のテストを単独で実行すると問題describe ".from_strava"ありませんが、ブロック全体を実行すると、メッセージで失敗します

Double :client was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.

言いたいことはわかるが、確かにこれはdouble2 つの例で使用されている a の適切な使用法です。結局のところ、clientdouble は例にとって重要ではありません。WebMock を使用できると思いますが、それは非常に低レベルのようで、実際に記述されたコードにうまく変換されません。結局のところ、例ごとに 1 つのことだけを主張する必要があります。

clientdouble を呼び出しに置き換えることを考えていました

allow(Strava::Api::V3::Client).to receive_message_chain(:new, :retrieve_an_activity) { json_response }

receive_message_chainしかし、ドキュメンテーションがコードの匂いであるべきだと述べていることを考えると、それも正しいアプローチではないようです 。

したがってreceive_message_chain、 , shared clientdouble を使用せず、標準の DRY 原則に従うべきではない場合、どうすればこれを修正できますか?

これについてのフィードバックをお待ちしています。

ありがとう、デイブ

4

4 に答える 4

8

確かに、これは 2 つの例で使用されている double の適切な使用法です。

いいえ、ちがいます。:) クラス変数を使用しようとしています。変数は例にまたがらないため、そうしないでください。解決策は、毎回、つまり各例でクライアントを設定することです。

悪い:

@client ||= Strava::Api::V3::Client.new(access_token: 'abc123')

良い:

@client = Strava::Api::V3::Client.new(access_token: 'abc123')
于 2014-11-23T21:15:19.200 に答える
3

私のアプリでも同じユースケースがあり、キャッシュをプライベートメソッドに抽出し、そのメソッドをスタブして double を返すことで解決しました (newメソッドを直接スタブするのではなく)。

たとえば、テスト対象のクラスでは次のようになります。

def self.from_strava(activity_id, race_id, user)
  activity = strava_client.retrieve_an_activity(activity_id)
  ...
end

private
def self.strava_client
  @client ||= Strava::Api::V3::Client.new(access_token: 'abc123')
end

そして仕様では:

let(:client) { double(:client) }

before { allow(described_class).to receive(:strava_client).and_return(client) }

...
于 2015-02-25T21:08:33.337 に答える
2

TLDR:ブロックafter { order.vendor_service = nil }のバランスを取るために追加します。beforeそれとも続きを読む...

私はこれに出くわしましたが、それがどこから来ているのかは明らかではありませんでした. order_spec.rb モデル テストでは、次のようにしました。

  describe 'order history' do
    before do
      service = double('VendorAPI')
      allow(service).to receive(:order_count).and_return(5)
      order.vendor_service = service
    end   

    # tests here  ..
  end

そして私のOrderモデルでは:

  def too_many_orders?
    @@vendor_service ||= VendorAPI.new(key: 'abc', account: '123')
    return @@vendor_service.order_count > 10
  end

order_spec.rb で rspec のみを実行した場合、これはうまくいきました

andのallow_any_instance_of()代わりに使用して、 order_controller_spec.rb でまったく異なるものを少し違った方法でモックしていました。doubleallow

  allow_any_instance_of(Order).to receive(:too_many_orders?).and_return(true)

これも、問題なくテストされました。

交絡する問題は、テストの完全なスイートを実行したときに、コントローラーのモックで OP のエラーが発生したことallow_any_instanceです。問題 (または少なくとも私の解決策) は、私が使用するモデル テストにあるため、これを追跡するのは非常に困難でしたdouble/allow

これを修正するためにafter、クラス変数 @@vendor_service をクリアするブロックを追加し、beforeブロックのアクションのバランスを取ります。

  describe 'order history' do
    before do
      service = double('VendorAPI')
      allow(service).to receive(:order_count).and_return(5)
      order.vendor_service = service
    end   

    after do
      order.vendor_service = nil 
    end

    # tests here  ..
  end

これにより、モックオブジェクトではなく、後の無関係なテストで ||= VendorAPI.new()実際の関数を使用することが強制されました。new

于 2015-11-04T02:25:58.163 に答える