如何使用Rspec测试一系列带有参数的方法链?

4

我有一个在Ruby模型中的方法调用,看起来像下面这样:

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

在models spec.rb文件中,我试图通过传递参数来模拟该调用并获取一个值。但我无法确定调用的正确方式。

在我的spec.rb文件顶部,我有:

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

在describe块中,我尝试了以下操作:

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)

大家可能已经猜到了,这些方法都不起作用。有人知道正确的方法吗?这种事情真的可行吗?

3个回答

9

一般来说,我不倾向于使用存根链,因为它们通常是违反迪米特法则的迹象。但是,如果必须使用,这是如何模拟该序列的方法:

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
这是我会做的。请注意,我将“mocking”部分和“expecting”部分分开,因为通常我会在下面有其他it示例(然后我需要这些it示例也具有相同的“mocked”逻辑),而且我更喜欢它们具有单独的关注点:即任何it示例内部的内容都应该专注于“expecting”,所以任何模拟或其他逻辑,我通常将它们放在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

如果您不了解Ruby的tap方法,请随意查看这里


1
我发现在测试Sequel gem的数据集链调用以获取数据时,这种方法特别有用,例如DB[:table].select(:column).where(id: 1).all。如果按照传统方式进行,您必须使用更多的代码模拟doubles,然后在链中返回这些doubles,而这种方法可以减少很多代码行数。 - himanish.k

0

我认为你需要像这样将链条重构为两行:

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)

然后,您可以编写规范,将该属性传递给find_by并检查链。


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