Skip callbacks on Factory Girl and Rspec

前端 未结 16 1009
暗喜
暗喜 2020-12-12 12:17

I\'m testing a model with an after create callback that I\'d like to run only on some occasions while testing. How can I skip/run callbacks from a factory?

c         


        
相关标签:
16条回答
  • 2020-12-12 13:08
    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
    

    Important note you should specify both of them. If only use before and run multiple specs, it'll try to disable callback multiple times. It'll succeed the first time, but on the second, callback isn't going to be defined anymore. So it'll error out

    0 讨论(0)
  • 2020-12-12 13:08

    A simple stub worked best for me in Rspec 3

    allow_any_instance_of(User).to receive_messages(:run_something => nil)
    
    0 讨论(0)
  • 2020-12-12 13:11

    Calling skip_callback from my factory proved problematic for me.

    In my case, I have a document class with some s3-related callbacks in before and after create that I only want to run when testing the full stack is necessary. Otherwise, I want to skip those s3 callbacks.

    When I tried skip_callbacks in my factory, it persisted that callback skip even when I created a document object directly, without using a factory. So instead, I used mocha stubs in the after build call and everything is working perfectly:

    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
    
    0 讨论(0)
  • 2020-12-12 13:11

    Here's a snippet I created to handle this in a generic way.
    It will skip every callback configured, including rails-related callbacks like before_save_collection_association, but it won't skip some needed to make ActiveRecord work ok, like autogenerated autosave_associated_records_for_ callbacks.

    # 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
    

    then later:

    create(:user, :skip_all_callbacks)
    

    Needless to say, YMMV, so take a look in the test logs what are you really skipping. Maybe you have a gem adding a callback you really need and it will make your tests to fail miserably or from your 100 callbacks fat model you just need a couple for a specific test. For those cases, try the transient :force_callbacks

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

    BONUS

    Sometimes you need also skip validations (all in a effort to make tests faster), then try with:

      trait :skip_validate do
        to_create { |instance| instance.save(validate: false) }
      end
    
    0 讨论(0)
提交回复
热议问题