Skip callbacks on Factory Girl and Rspec

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

    When you don't want to run a callback do the following:

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

    Be aware that skip_callback will be persistant across other specs after it is run therefore consider something like the following:

    before do
      User.skip_callback(:create, :after, :run_something)
    end
    
    after do
      User.set_callback(:create, :after, :run_something)
    end
    
    0 讨论(0)
  • 2020-12-12 12:58

    I'm not sure if it is the best solution, but I have successfully achieved this using:

    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
    

    Running without callback:

    FactoryGirl.create(:user)
    

    Running with callback:

    FactoryGirl.create(:user_with_run_something)
    
    0 讨论(0)
  • 2020-12-12 12:58

    James Chevalier's answer about how to skip before_validation callback didn't help me so if you straggle the same as me here is working solution:

    in model:

    before_validation :run_something, on: :create
    

    in factory:

    after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) }
    
    0 讨论(0)
  • 2020-12-12 12:59

    Rails 5 - skip_callback raising Argument error when skipping from a FactoryBot factory.

    ArgumentError: After commit callback :whatever_callback has not been defined
    

    There was a change in Rails 5 with how skip_callback handles unrecognized callbacks:

    ActiveSupport::Callbacks#skip_callback now raises an ArgumentError if an unrecognized callback is remove

    When skip_callback is called from the factory, the real callback in the AR model is not yet defined.

    If you've tried everything and pulled your hair out like me, here is your solution (got it from searching FactoryBot issues) (NOTE the raise: false part):

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

    Feel free to use it with whatever other strategies you prefer.

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

    This will work with current rspec syntax (as of this post) and is much cleaner:

    before do
       User.any_instance.stub :run_something
    end
    
    0 讨论(0)
  • 2020-12-12 13:04

    I'd like to make an improvement to @luizbranco 's answer to make after_save callback more reusable when creating other users.

    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
    

    Running without after_save callback:

    FactoryGirl.create(:user)
    

    Running with after_save callback:

    FactoryGirl.create(:user, :with_after_save_callback)
    

    In my test, I prefer to create users without the callback by default because the methods used run extra stuff I don't normally want in my test examples.

    ----------UPDATE------------ I stopped using skip_callback because there were some inconsistency issues in the test suite.

    Alternative Solution 1 (use of stub and unstub):

    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
    

    Alternative Solution 2 (my preferred approach):

    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
    
    0 讨论(0)
提交回复
热议问题