【问题标题】:How do I 'expect' a chain of methods using Rspec where the first method takes a parameter?我如何“期望”使用 Rspec 的方法链,其中第一个方法采用参数?
【发布时间】:2018-08-19 21:25:51
【问题描述】:

我在 ruby​​ 模型中有一个方法调用,如下所示:

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

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

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

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

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

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)

您可能已经猜到了,这些都不起作用。有谁知道做这种事情的正确方法?有没有可能?

【问题讨论】:

    标签: ruby-on-rails ruby rspec mocking


    【解决方案1】:

    一般来说,我不喜欢使用存根链,因为它们通常表明您违反了Law of Demeter。但是,如果必须,我会这样模拟该序列:

    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】:

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

      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 并检查链。

      【讨论】:

        【解决方案3】:

        这就是我会做的。请注意,我将“模拟”部分和“预期”部分分开,因为通常我会在下面有一些其他 it 示例(然后我需要那些 it 示例也有相同的“模拟” " 逻辑),并且因为我更喜欢它们有单独的关注点:这是 it 示例中的任何内容通常应该只关注“预期”,因此任何模拟或其他逻辑,我通常将它们放在 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 方法,请随时 check this out

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-06-30
          • 1970-01-01
          相关资源
          最近更新 更多