15

我有一個ActiveRecord模型,PricePackage。這有一個before_create回調。此回調使用第三方API進行遠程連接。我正在使用工廠女孩,並希望將此API廢除,以便在測試過程中建立新工廠時不會進行遠程調用。如何模擬和存根活動記錄before_create與factory_girl回調

我使用Rspec模擬和存根。我遇到的問題是,Rspec的方法不可用我的factories.rb內

型號:

class PricePackage < ActiveRecord::Base 
    has_many :users 
    before_create :register_with_3rdparty 

    attr_accessible :price, :price_in_dollars, :price_in_cents, :title 


    def register_with_3rdparty 
     return true if self.price.nil? 

     begin 
      3rdPartyClass::Plan.create(
      :amount => self.price_in_cents, 
      :interval => 'month', 
      :name => "#{::Rails.env} Item #{self.title}", 
      :currency => 'usd', 
      :id => self.title) 
     rescue Exception => ex 
      puts "stripe exception #{self.title} #{ex}, using existing price" 
      plan = 3rdPartyClass::Plan.retrieve(self.title) 
      self.price_in_cents = plan.amount 
      return true 
     end 
    end 

廠:

#PricePackage 
Factory.define :price_package do |f| 
    f.title "test_package" 
    f.price_in_cents "500" 
    f.max_domains "20" 
    f.max_users "4" 
    f.max_apps "10" 
    f.after_build do |pp| 
    # 
    #heres where would like to mock out the 3rd party response 
    # 
    3rd_party = mock() 
    3rd_party.stub!(:amount).price_in_cents 
    3rdPartyClass::Plan.stub!(:create).and_return(3rd_party) 
    end 
end 

我不知道怎麼弄的rspec模擬和存根助手加載到我的factories.rb中,這可能不是處理這個問題的最好方法。

+1

順便說一句,當你將一個賞金給獎金將從您的名譽採取的一個問題,無論您指定所以這對f來說是件好事通過它並分配給人們給出的答案之一。沒有這樣做,它只是蒸發 –

+0

'after_build'中的'pp.stub(:register_with_3rdparty){true}'會引發任何錯誤嗎? – lulalala

回答

19

作爲VCR寶石的作者,你可能會期望我推薦它用於這些情況。我確實推薦它用於測試依賴於HTTP的代碼,但我認爲您的設計存在潛在的問題。不要忘了,TDD(測試驅動開發)是一個設計原則,當你發現很容易測試某些東西時,它會告訴你一些關於你的設計的東西。聽聽你的測試的痛苦!

在這種情況下,我認爲您的模型沒有業務進行第三方API調用。這是對單一責任原則的相當重大的違反。模型應該對一些數據的驗證和持久性負責,但這肯定超出了這個範圍。

相反,我建議您將第三方API調用移到觀察者中。帕特馬多克斯有一個great blog post討論如何觀察員可以(而且應該)鬆散耦合的東西沒有違反SRP(單一責任原則),以及如何使測試,更容易,也提高了你的設計。

一旦你將它移動到觀察者中,很容易在單元測試中禁用觀察者(對於那個觀察者的特定測試除外),但在生產和集成測試中保持啓用狀態。您可以使用Pat的no-peeping-toms插件來提供幫助,或者,如果您使用的是rails 3.1,則應該查看內置於ActiveModel的new functionality,以便您可以easily enable/disable observers

+0

什麼如果API調用*是持久性(有點像ActiveResource所做的那樣)? * *不適合模型。 –

+0

當然,如果數據是通過HTTP API持久保存的,那麼是的,這將是模型的主要責任,並且它絕對應該在模型中(或超類或模塊混入模型中)。請注意,我表示「第三方API通話」。我絕不會認爲你的持久性是第三方API。 –

+0

對,我明白你的觀點。感謝您的澄清。 –

-1

嗯,首先,你說得對,「模擬和存根」不是的工廠女孩​​

猜測你的模型關係的語言,我想你會想建立另一個對象工廠,設置其屬性,然後關聯它們。

#PricePackage 
Factory.define :price_package do |f| 
    f.title "test_package" 
    f.price_in_cents "500" 
    f.max_domains "20" 
    f.max_users "4" 
    f.max_apps "10" 
    f.after_build do |pp| 
    f.3rdClass { Factory(:3rd_party) } 
end 

Factory.define :3rd_party do |tp| 
    tp.price_in_cents = 1000 
end 

希望我沒有弄亂關係不可信。

+0

'price_package'和第三方內容之間沒有數據關聯。我添加了一個我的模型的例子。這有助於證明在rails'before_create'回調中調用3rdparty api。 所以我想在register_with_3rdparty方法中對該部分進行存根和模擬。因此,每當創建新的'price_package'工廠時,工廠女孩都不會直接連接。 – kevzettler

1

結帳VCR寶石(https://www.relishapp.com/myronmarston/vcr)。它會記錄您的測試套件的HTTP交互併爲您回放。刪除任何實際建立到第三方API的HTTP連接的要求。我發現這是一種比手動模擬交互更簡單的方法。這裏有一個使用Foursquare庫的例子。

VCR.config do |c| 
    c.cassette_library_dir = 'test/cassettes' 
    c.stub_with :faraday 
end 

describe Checkin do 
    it 'must check you in to a location' do 
    VCR.use_cassette('foursquare_checkin') do 
     Skittles.checkin('abcd1234') # Doesn't actually make any HTTP calls. 
            # Just plays back the foursquare_checkin VCR 
            # cassette. 
    end 
    end 
end 
0

FactoryGirl可以存根出一個對象的屬性,也許可以幫助你:

# Returns an object with all defined attributes stubbed out 
stub = FactoryGirl.build_stubbed(:user) 

可以在FactoryGirl's rdocs

1

找到更多的信息。雖然我可以看到在封裝方面的吸引力,第三方存根不必在您的工廠內發生(並且在某些方面可能不應該發生)。

而不是在工廠封裝它,你可以簡單地在你的RSpec測試開始時定義它。這樣做還可以確保您的測試假設在開始時清晰明瞭(在調試時這可能非常有用)

在使用PricePlan進行任何測試之前,請設置所需的響應,然後從第三方返回.create方法:

before(:all) do 
    3rd_party = mock('ThirdParty') 
    3rdPartyClass::Plan.stub(:create).and_return(true) 
end 

這應該允許您調用該方法,但會關閉遠程調用。

*它看起來像你的第三方存根對原始對象(:price_in_cents)有一定的依賴關係,但不知道更多關於確切的依賴關係,我無法猜測什麼是合適的存根(或者是否有必要)*

+0

似乎沒有工作:TypeError: #不是類/模塊 – avioing

+0

這是另一個問題,這種方法...通常,我會全心全意地同意WRT保持測試乾淨。然而,在這種情況下,由於該模型可能會直接或間接地用於許多測試中,所以您必須在每次測試中都這樣做......因此@kevzettler尋找封裝(工廠內部) – avioing

0

我有同樣的確切問題。觀察一邊討論(這可能是正確的方法),這裏是我工作(這是一個開始,可以/應該加以改進):

添加文件3rdparty.rb符合規範與這些內容/支:

RSpec.configure do |config| 
    config.before do 
    stub(3rdPartyClass::Plan).create do 
    [add stuff here] 
    end 
    end 
end 

,並確保您的spec_helper.rb有這樣的:

Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }