फैक्ट्री गर्ल और Rspec पर कॉलबैक छोड़ें


103

मैं एक मॉडल बना रहा हूँ जिसमें कॉलबैक के बाद मैं परीक्षण के दौरान कुछ अवसरों पर ही चलना चाहूँगा। मैं किसी कारखाने से कॉलबैक को कैसे छोड़ / चला सकता हूं?

class User < ActiveRecord::Base
  after_create :run_something
  ...
end

फैक्टरी:

FactoryGirl.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"
    ...
    # skip callback

    factory :with_run_something do
      # run callback
  end
end

जवाबों:


111

मुझे यकीन नहीं है कि यह सबसे अच्छा समाधान है, लेकिन मैंने इसका उपयोग करके सफलतापूर्वक हासिल किया है:

FactoryGirl.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"
    #...

    after(:build) { |user| user.class.skip_callback(:create, :after, :run_something) }

    factory :user_with_run_something do
      after(:create) { |user| user.send(:run_something) }
    end
  end
end

कॉलबैक के बिना चल रहा है:

FactoryGirl.create(:user)

कॉलबैक से चल रहा है:

FactoryGirl.create(:user_with_run_something)

3
यदि आप एक :on => :createसत्यापन छोड़ना चाहते हैं , तो उपयोग करेंafter(:build) { |user| user.class.skip_callback(:validate, :create, :after, :run_something) }
जेम्स शेवेलियर

7
बेहतर नहीं होगा कि स्किपिंग कॉलबैक लॉजिक को उल्टा कर दें? मेरा मतलब है, डिफ़ॉल्ट यह होना चाहिए कि जब मैं एक ऑब्जेक्ट बनाता हूं तो कॉलबैक ट्रिगर हो जाते हैं, और मुझे असाधारण मामले के लिए एक अलग पैरामीटर का उपयोग करना चाहिए। इसलिए FactoryGirl.create (: उपयोगकर्ता) कॉलबैक ट्रिगर करने वाले उपयोगकर्ता को बनाना चाहिए, और FactoryGirl.create (: user_without_callbacks) को कॉलबैक के बिना उपयोगकर्ता बनाना चाहिए। मुझे पता है कि यह केवल एक "डिज़ाइन" संशोधन है, लेकिन मुझे लगता है कि यह पहले से मौजूद कोड को तोड़ने से बच सकता है, और अधिक सुसंगत हो सकता है।
गगनो

3
@ मिनिमल सॉल्यूशन नोट्स के रूप में, Class.skip_callbackकॉल अन्य परीक्षणों में लगातार रहेगा, इसलिए यदि आपके अन्य परीक्षण कॉलबैक होने की उम्मीद करते हैं, तो आप असफल कॉलबैक लॉजिक को उल्टा करने की कोशिश करने पर विफल हो जाएंगे।
MPdaugherty

मैंने after(:build)ब्लॉक में मोचा के साथ ठुमके लगाने के बारे में uberllama के जवाब का उपयोग करके समाप्त किया । यह आपके कारखाने को कॉलबैक चलाने के लिए डिफ़ॉल्ट बनाता है और हर उपयोग के बाद कॉलबैक को रीसेट करने की आवश्यकता नहीं होती है।
3

क्या आपके पास इस तरह से काम करने का कोई विचार है? stackoverflow.com/questions/35950470/…
क्रिस हफ

89

जब आप कॉलबैक चलाना नहीं चाहते हैं तो निम्न कार्य करें:

User.skip_callback(:create, :after, :run_something)
Factory.create(:user)

ध्यान रखें कि Skip_callback अन्य स्पेक्स के बाद भी बना रहेगा क्योंकि इसे चलाया जाता है इसलिए निम्न बातों पर विचार करें:

before do
  User.skip_callback(:create, :after, :run_something)
end

after do
  User.set_callback(:create, :after, :run_something)
end

12
मुझे यह उत्तर बेहतर लगता है क्योंकि यह स्पष्ट रूप से बताता है कि स्किपिंग कॉलबैक कक्षा स्तर पर घूमता रहता है, और इसलिए बाद के परीक्षणों में कॉलबैक को छोड़ना जारी रखेगा।
सियानोपोलो

मुझे यह भी अच्छा लगता है। मैं नहीं चाहता कि मेरा कारखाना स्थायी रूप से अलग व्यवहार करे। मैं इसे परीक्षण के एक विशेष सेट के लिए छोड़ना चाहता हूं।
TheUtherSide

39

इनमें से कोई भी समाधान अच्छा नहीं है। वे उस कार्यक्षमता को हटाकर वर्ग को दोष देते हैं जिसे उदाहरण से हटाया जाना चाहिए, वर्ग से नहीं।

factory :user do
  before(:create){|user| user.define_singleton_method(:send_welcome_email){}}

कॉलबैक को दबाने के बजाय, मैं कॉलबैक की कार्यक्षमता को दबा रहा हूं। एक तरह से, मुझे यह दृष्टिकोण बेहतर लगता है क्योंकि यह अधिक स्पष्ट है।


1
मैं वास्तव में इस जवाब को पसंद करता हूं, और आश्चर्यचकित करता हूं कि क्या ऐसा कुछ है, अलियास किया गया ताकि इरादा तुरंत स्पष्ट हो, खुद FactoryGirl का हिस्सा होना चाहिए।
ग्यूसेप

मुझे यह उत्तर भी पसंद है कि मैं सब कुछ छोड़ दूंगा, लेकिन ऐसा प्रतीत होता है कि हमें परिभाषित पद्धति से एक ब्लॉक को पारित करने की आवश्यकता है, अगर यह आपके कॉलबैक की तरह है around_*(जैसे user.define_singleton_method(:around_callback_method){|&b| b.call })।
Quv

1
न केवल एक बेहतर समाधान, बल्कि किसी कारण से अन्य विधि मेरे लिए काम नहीं करती थी। जब मैंने इसे लागू किया तो यह कहा कि कोई कॉलबैक विधि मौजूद नहीं थी लेकिन जब मैंने इसे छोड़ा तो यह मुझे अनावश्यक अनुरोधों को ठुकराने के लिए कहेगी। हालांकि यह मुझे एक समाधान के लिए ले जाता है क्या किसी को पता है कि ऐसा क्यों हो सकता है?
Babbz77

27

मैं अन्य उपयोगकर्ताओं को बनाने के दौरान after_save कॉलबैक को अधिक पुन: प्रयोज्य बनाने के लिए @luizbranco के उत्तर में सुधार करना चाहता हूं।

FactoryGirl.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"
    #...

    after(:build) { |user| 
      user.class.skip_callback(:create, 
                               :after, 
                               :run_something1,
                               :run_something2) 
    }

    trait :with_after_save_callback do
      after(:build) { |user| 
        user.class.set_callback(:create, 
                                :after, 
                                :run_something1,
                                :run_something2) 
      }
    end
  end
end

After_save कॉलबैक के बिना चल रहा है:

FactoryGirl.create(:user)

After_save कॉलबैक के साथ चल रहा है:

FactoryGirl.create(:user, :with_after_save_callback)

अपने परीक्षण में, मैं डिफ़ॉल्ट रूप से कॉलबैक के बिना उपयोगकर्ताओं को बनाना पसंद करता हूं, क्योंकि जिन तरीकों से अतिरिक्त सामान का उपयोग किया जाता है, वे सामान्य रूप से मेरे परीक्षण के उदाहरणों में नहीं चाहिए।

---------- अद्यतन ------------ मैंने Skip_callback का उपयोग करना बंद कर दिया क्योंकि परीक्षण सूट में कुछ असंगतता के मुद्दे थे।

वैकल्पिक समाधान 1 (स्टब और अनस्टब का उपयोग):

after(:build) { |user| 
  user.class.any_instance.stub(:run_something1)
  user.class.any_instance.stub(:run_something2)
}

trait :with_after_save_callback do
  after(:build) { |user| 
    user.class.any_instance.unstub(:run_something1)
    user.class.any_instance.unstub(:run_something2)
  }
end

वैकल्पिक समाधान 2 (मेरा पसंदीदा तरीका):

after(:build) { |user| 
  class << user
    def run_something1; true; end
    def run_something2; true; end
  end
}

trait :with_after_save_callback do
  after(:build) { |user| 
    class << user
      def run_something1; super; end
      def run_something2; super; end
    end
  }
end

क्या आपके पास इस तरह से काम करने का कोई विचार है? stackoverflow.com/questions/35950470/…
क्रिस हफ

RuboCop वैकल्पिक समाधान 2 के लिए "स्टाइल / सिंगललाइनमैथोड्स: सिंगल-लाइन मेथड परिभाषाओं से बचें" के साथ शिकायत करता है, इसलिए मुझे फ़ॉर्मेटिंग को बदलने की आवश्यकता होगी, लेकिन अन्यथा यह सही है!
coberlin

14

5 - skip_callbackएक FactoryBot कारखाने से लंघन जब तर्क त्रुटि बढ़ रही है।

ArgumentError: After commit callback :whatever_callback has not been defined

रेल 5 में एक बदलाव था कि कैसे अनजाने कॉलबैक को स्किप_कॉलबैक हैंडल करता है:

ActiveSupport :: Callbacks # Skip_callback अब एक गैर मान्यता प्राप्त कॉलबैक को हटा देने पर एक ArgumentError उठाता है

जब skip_callbackकारखाने से कॉल किया जाता है, तो एआर मॉडल में वास्तविक कॉलबैक को अभी तक परिभाषित नहीं किया गया है।

यदि आपने सबकुछ आज़माया है और अपने बालों को मेरी तरह खींचा है, तो यहाँ आपका समाधान है (इसे FactoryBot मुद्दों को खोजने से प्राप्त करें) ( भाग पर ध्यान देंraise: false ):

after(:build) { YourSweetModel.skip_callback(:commit, :after, :whatever_callback, raise: false) }

जो भी अन्य रणनीति आप पसंद करते हैं, उसके साथ इसका उपयोग करने के लिए स्वतंत्र महसूस करें।


1
महान, यह वास्तव में मेरे साथ क्या हुआ है। ध्यान दें कि यदि आपने एक बार कॉलबैक हटा दिया है, और इसे फिर से आज़माएं, तो ऐसा होता है, इसलिए यह काफी संभावना है कि यह एक कारखाने के लिए कई बार चालू हो जाएगा।
14

6

यह समाधान मेरे लिए काम करता है और आपको अपनी फैक्टरी परिभाषा में एक अतिरिक्त ब्लॉक जोड़ना होगा:

user = FactoryGirl.build(:user)
user.send(:create_without_callbacks) # Skip callback

user = FactoryGirl.create(:user)     # Execute callbacks

5

एक साधारण ठूंठ ने मेरे लिए Rspec 3 में सबसे अच्छा काम किया

allow(User).to receive_messages(:run_something => nil)

4
आपको इसे उदाहरणों के लिए सेट करना होगा User; :run_somethingएक वर्ग विधि नहीं है।
PJSCopeland

5
FactoryGirl.define do
  factory :order, class: Spree::Order do

    trait :without_callbacks do
      after(:build) do |order|
        order.class.skip_callback :save, :before, :update_status!
      end

      after(:create) do |order|
        order.class.set_callback :save, :before, :update_status!
      end
    end
  end
end

महत्वपूर्ण नोट आपको दोनों को निर्दिष्ट करना चाहिए। यदि पहले केवल कई स्पेक्स का उपयोग करें और चलाएं, तो यह कई बार कॉलबैक को अक्षम करने का प्रयास करेगा। यह पहली बार सफल होगा, लेकिन दूसरे पर, कॉलबैक को अब परिभाषित नहीं किया जाएगा। तो यह बाहर त्रुटि होगी


इसने हाल ही में एक परियोजना पर एक सूट में कुछ अप्रिय विफलताएं पैदा कीं - मेरे पास @ साईराम के उत्तर के समान कुछ था लेकिन कॉलबैक को परीक्षणों के बीच कक्षा में छोड़ दिया जा रहा था। ओह।
kfrz

4

मेरे कारखाने से Skip_callback को कॉल करना मेरे लिए समस्याग्रस्त साबित हुआ।

मेरे मामले में, मेरे पास पहले और बाद में कुछ s3- संबंधित कॉलबैक के साथ एक दस्तावेज़ वर्ग है जिसे बनाने के बाद मैं केवल तभी चलाना चाहता हूं जब पूर्ण स्टैक का परीक्षण करना आवश्यक हो। अन्यथा, मैं उन s3 कॉलबैक को छोड़ना चाहता हूं।

जब मैंने अपने कारखाने में Skip_callbacks की कोशिश की, तो यह एक कॉल का उपयोग किए बिना सीधे कॉलबैक स्किप को बनाए रखने पर भी कायम रहा। इसलिए इसके बजाय, मैंने बिल्ड कॉल के बाद मोचा स्टब्स का इस्तेमाल किया और सब कुछ पूरी तरह से काम कर रहा है:

factory :document do
  upload_file_name "file.txt"
  upload_content_type "text/plain"
  upload_file_size 1.kilobyte
  after(:build) do |document|
    document.stubs(:name_of_before_create_method).returns(true)
    document.stubs(:name_of_after_create_method).returns(true)
  end
end

सभी समाधान यहाँ से, और कारखाने के भीतर तर्क होने के लिए, यह केवल एक ही है कि एक के साथ काम करता है before_validationहुक (करने का प्रयास कर skip_callbackFactoryGirl के से किसी के साथ beforeया afterके लिए विकल्प buildऔर createकाम नहीं किया था)
माइक टी

3

यह वर्तमान rspec सिंटैक्स (इस पोस्ट के रूप में) के साथ काम करेगा और बहुत क्लीनर है:

before do
   User.any_instance.stub :run_something
end

यह Rspec 3 में दर्शाया गया है। मेरे लिए काम किए गए एक नियमित ठूंठ का उपयोग करके, नीचे मेरा जवाब देखें।
samg

3

जेम्स शेवेलियर के जवाब से पहले कैसे छोड़ें_वापस कॉलबैक ने मेरी मदद नहीं की, यदि आप मुझे यहां पर उसी तरह से डगमगाते हैं जैसे कि मैं यहां काम कर रहा हूं:

मॉडल में:

before_validation :run_something, on: :create

फैक्ट्री में:

after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) }

2
मुझे लगता है कि इससे बचना बेहतर है। यह वर्ग के हर उदाहरण के लिए कॉलबैक को छोड़ देता है (न कि केवल कारखाना लड़की द्वारा उत्पन्न)। यह कुछ कल्पना निष्पादन मुद्दों को जन्म देगा (यानी यदि प्रारंभिक कारखाने के निर्माण के बाद अक्षम होता है) तो डिबग करना मुश्किल हो सकता है। यदि यह कल्पना / समर्थन में वांछित व्यवहार है तो इसे स्पष्ट रूप से किया जाना चाहिए: Model.skip_callback(...)
केविन सिल्वेस्ट्रे

2

मेरे मामले में मेरे पास मेरे रेडिस कैश में कुछ लोड करने के लिए कॉलबैक है। लेकिन तब मेरे पास मेरे परीक्षण वातावरण के लिए चल रहे एक लाल उदाहरण नहीं था।

after_create :load_to_cache

def load_to_cache
  Redis.load_to_cache
end

मेरी स्थिति के लिए, उपरोक्त के समान, मैंने load_to_cacheअपने युक्ति_हेल्पर में अपनी विधि को ठूंसा, साथ:

Redis.stub(:load_to_cache)

इसके अलावा, कुछ विशेष परिस्थितियों में जहां मैं यह परीक्षण करना चाहता हूं, मुझे बस उन्हें संबंधित आरएसपी परीक्षण मामलों के पहले ब्लॉक में अनस्टब करना होगा।

मुझे पता है कि आपके पास कुछ और अधिक जटिल हो after_createसकता है या आपको यह बहुत सुंदर नहीं लग सकता है। आप after_createअपने कारखाने में एक हुक को परिभाषित करके अपने कारखाने में परिभाषित कॉलबैक को रद्द करने का प्रयास कर सकते हैं (factory_girl डॉक्स को देखें), जहां आप शायद falseइस लेख के 'कैंसिलिंग कॉलबैक' अनुभाग के अनुसार एक ही कॉलबैक और रिटर्न को परिभाषित कर सकते हैं । (मैं उस आदेश के बारे में अनिश्चित हूं जिसमें कॉलबैक निष्पादित किया गया है, यही वजह है कि मैं इस विकल्प के लिए नहीं गया था)।

अंत में, (क्षमा करें, मैं लेख नहीं पा रहा हूं) रूबी आपको कॉलबैक हुक को अनहुक करने के लिए कुछ गंदे मेटा प्रोग्रामिंग का उपयोग करने की अनुमति देता है (आपको इसे रीसेट करना होगा)। मुझे लगता है कि यह सबसे कम पसंदीदा विकल्प होगा।

वैसे एक और बात है, वास्तव में एक समाधान नहीं है, लेकिन देखें कि क्या आप वास्तव में ऑब्जेक्ट बनाने के बजाय अपने चश्मे में Factory.build के साथ भाग सकते हैं। (यदि आप कर सकते हैं तो सबसे सरल होगा)।


2

ऊपर पोस्ट किए गए उत्तर के बारे में, https://stackoverflow.com/a/35562805/2001785 , आपको फ़ैक्टरी में कोड जोड़ने की आवश्यकता नहीं है। मुझे यह आसान लगा कि मैं खुद ही स्पेक्स में तरीकों को ओवरलोड कर सकता हूं। उदाहरण के लिए, इसके बजाय (उद्धृत पोस्ट में फ़ैक्टरी कोड के साथ संयोजन के रूप में)

let(:user) { FactoryGirl.create(:user) }

मुझे उपयोग करना पसंद है (उद्धृत कारखाने कोड के बिना)

let(:user) do
  FactoryGirl.build(:user).tap do |u|
      u.define_singleton_method(:send_welcome_email){}
      u.save!
    end
  end
end

इस तरह आपको परीक्षण के व्यवहार को समझने के लिए कारखाने और परीक्षण फ़ाइलों दोनों को देखने की आवश्यकता नहीं है।


1

मैंने निम्न समाधान को एक क्लीनर तरीके के रूप में पाया क्योंकि कॉलबैक रन / सेट एक वर्ग स्तर पर है।

# create(:user) - will skip the callback.
# create(:user, skip_create_callback: false) - will set the callback
FactoryBot.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"

    transient do
      skip_create_callback true
    end

    after(:build) do |user, evaluator|
      if evaluator.skip_create_callback
        user.class.skip_callback(:create, :after, :run_something)
      else
        user.class.set_callback(:create, :after, :run_something)
      end
    end
  end
end

0

यहाँ मैंने एक सामान्य तरीके से इसे संभालने के लिए एक स्निपेट बनाया है।
यह कॉन्फ़िगर किए गए प्रत्येक कॉलबैक को छोड़ देगा, जिसमें रेल-संबंधित कॉलबैक जैसे शामिल हैं before_save_collection_association, लेकिन यह ऑटोरेंजेनेटेड autosave_associated_records_for_कॉलबैक की तरह, ActiveRecord को ठीक बनाने के लिए आवश्यक कुछ को छोड़ नहीं देगा ।

# In some factories/generic_traits.rb file or something like that
FactoryBot.define do
  trait :skip_all_callbacks do
    transient do
      force_callbacks { [] }
    end

    after(:build) do |instance, evaluator|
      klass = instance.class
      # I think with these callback types should be enough, but for a full
      # list, check `ActiveRecord::Callbacks::CALLBACKS`
      %i[commit create destroy save touch update].each do |type|
        callbacks = klass.send("_#{type}_callbacks")
        next if callbacks.empty?

        callbacks.each do |cb|
          # Autogenerated ActiveRecord after_create/after_update callbacks like
          # `autosave_associated_records_for_xxxx` won't be skipped, also
          # before_destroy callbacks with a number like 70351699301300 (maybe
          # an Object ID?, no idea)
          next if cb.filter.to_s =~ /(autosave_associated|\d+)/

          cb_name = "#{klass}.#{cb.kind}_#{type}(:#{cb.filter})"
          if evaluator.force_callbacks.include?(cb.filter)
            next Rails.logger.debug "Forcing #{cb_name} callback"
          end

          Rails.logger.debug "Skipping #{cb_name} callback"
          instance.define_singleton_method(cb.filter) {}
        end
      end
    end
  end
end

फिर बाद में:

create(:user, :skip_all_callbacks)

कहने की ज़रूरत नहीं है, YMMV, इसलिए परीक्षण लॉग में एक नज़र डालें कि आप वास्तव में क्या छोड़ रहे हैं। हो सकता है कि आपके पास एक ऐसा रत्न हो जिसे कॉलबैक आपको वास्तव में चाहिए और यह आपके परीक्षणों को बुरी तरह से विफल करने के लिए या आपके 100 कॉलबैक वसा मॉडल से आपको बस एक विशिष्ट परीक्षण के लिए एक जोड़े की आवश्यकता होगी। उन मामलों के लिए, क्षणिक प्रयास करें:force_callbacks

create(:user, :skip_all_callbacks, force_callbacks: [:some_important_callback])

बक्शीश

कभी-कभी आपको मान्यताओं को छोड़ना पड़ता है (सभी परीक्षण तेज करने के प्रयास में), फिर प्रयास करें:

  trait :skip_validate do
    to_create { |instance| instance.save(validate: false) }
  end

-1
FactoryGirl.define do
 factory :user do
   first_name "Luiz"
   last_name "Branco"
   #...

after(:build) { |user| user.class.skip_callback(:create, :after, :run_something) }

trait :user_with_run_something do
  after(:create) { |user| user.class.set_callback(:create, :after, :run_something) }
  end
 end
end

जब आप इसे चलाना चाहते हैं, तो आप केवल उन उदाहरणों के लिए कॉलबैक सेट कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.