Skip callbacks on Factory Girl and Rspec

前端 未结 16 1045
暗喜
暗喜 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: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
    

提交回复
热议问题