How do I 'expect' a chain of methods using Rspec where the first method takes a parameter?

回眸只為那壹抹淺笑 提交于 2019-12-24 10:45:59

问题


I have a method call in a ruby model that looks like the following:

Contentful::PartnerCampaign.find_by(vanityUrl: referral_source).load.first

Within the models spec.rb file, I'm trying to mock that call and get a value by passing in a param. But I'm having trouble figuring out the correct way of calling it.

At the top of my spec.rb file I have:

 let(:first_double) { 
   double("Contentful::Model", fields {:promotion_type => "Promotion 1"}) 
 }

Within the describe block I've tried the following:

expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by, :load, :first).
                                       and_return(first_double)

expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by, :load, :first).with(vanityUrl: 'test_promo_path').
                                       and_return(first_double)

expect(Contentful::PartnerCampaign).to receive_message_chain(:find_by => vanityUrl: 'test_promo_path', :load, :first).
                                       and_return(first_double)

As you can probably guess, none of these are working. Does anyone know the correct way to do this sort of thing? Is it even possible?


回答1:


Generally speaking, I prefer not to use stub chains, as they are often a sign that you are violating the Law of Demeter. But, if I had to, this is how I would mock that sequence:

let(:vanity_url) { 'https://vanity.url' }
let(:partner_campaigns) { double('partner_campaigns') }
let(:loaded_partner_campaigns) { double('loaded_partner_campaigns') }

let(:partner_campaign) do
  double("Contentful::Model", fields {:promotion_type => "Promotion 1"}
end

before do
  allow(Contentful::PartnerCampaign)
    .to receive(:find_by)
    .with(vanity_url: vanity_url)
    .and_return(partner_campaigns)

  allow(partner_campaigns)
    .to receive(:load)
    .and_return(loaded_partner_campaigns)

  allow(loaded_partner_campaigns)
    .to receive(:first)
    .and_return(partner_campaign)
end



回答2:


I think you need to refactor the chain in two lines like this:

model    = double("Contentful::Model", fields: { promotion_type: "Promotion 1" }) 
campaign = double

allow(Contentful::PartnerCampaign).to receive(:find_by).with(vanityUrl: 'test_promo_path').and_return(campaign)
allow(campaign).to receive_message_chain(:load, :first).and_return(model)

Then you can write your spec that will pass that attribute to find_by and check the chain.




回答3:


This is what I would do. Notice that I split the "mocking" part and the "expecting" part, because usually I'll have some other it examples down below (of which then I'll need those it examples to also have the same "mocked" logic), and because I prefer them to have separate concerns: that is anything inside the it example should just normally focus on "expecting", and so any mocks or other logic, I normally put them outside the it.

let(:expected_referral_source) { 'test_promo_path' }
let(:contentful_model_double) { instance_double(Contentful::Model, promotion_type: 'Promotion 1') }

before(:each) do
  # mock return values chain
  # note that you are not "expecting" anything yet here
  # you're just basically saying that: if Contentful::PartnerCampaign.find_by(vanityUrl: expected_referral_source).load.first is called, that it should return contentful_model_double
  allow(Contentful::PartnerCampaign).to receive(:find_by).with(vanityUrl: expected_referral_source) do
    double.tap do |find_by_returned_object|
      allow(find_by_returned_object).to receive(:load) do
        double.tap do |load_returned_object|
          allow(load_returned_object).to receive(:first).and_return(contentful_model_double)
        end
      end
    end
  end
end

it 'calls Contentful::PartnerCampaign.find_by(vanityUrl: referral_source).load.first' do
  expect(Contentful::PartnerCampaign).to receive(:find_by).once do |argument|
    expect(argument).to eq({ vanityUrl: expected_referral_source})

    double.tap do |find_by_returned_object|
      expect(find_by_returned_object).to receive(:load).once do
        double.tap do |load_returned_object|
          expect(load_returned_object).to receive(:first).once
        end
      end
    end
  end
end

it 'does something...' do
  # ...
end

it 'does some other thing...' do
  # ...
end

If you do not know about ruby's tap method, feel free to check this out



来源:https://stackoverflow.com/questions/51921964/how-do-i-expect-a-chain-of-methods-using-rspec-where-the-first-method-takes-a

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!