Skip callbacks on Factory Girl and Rspec

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

    Regarding the answer posted above, https://stackoverflow.com/a/35562805/2001785, you do not need to add the code to the factory. I found it easier to overload the methods in the specs themselves. For example, instead of (in conjunction with the factory code in the cited post)

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

    I like using (without the cited factory code)

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

    This way you do not need to look at both the factory and the test files to understand the behavior of the test.

    0 讨论(0)
  • 2020-12-12 12:50

    In my case I have the callback loading something to my redis cache. But then I did not have/want a redis instance running for my test environment.

    after_create :load_to_cache
    
    def load_to_cache
      Redis.load_to_cache
    end
    

    For my situation, similar to above, I just stubbed my load_to_cache method in my spec_helper, with:

    Redis.stub(:load_to_cache)
    

    Also, in certain situation where I want to the test this, I just have to unstub them in the before block of the corresponding Rspec test cases.

    I know you might have something more complicated happening in your after_create or might not find this very elegant. You can try to cancel the callback defined in your model, by defining an after_create hook in your Factory (refer to factory_girl docs), where you can probably define a the same callback and return false, according to the 'Canceling callbacks' section of this article. (I am unsure about order in which callback are executed, which is why I didn't go for this option).

    Lastly, (sorry I am not able to find the article) Ruby allows you to use some dirty meta programming to unhook a callback hook (you will have to reset it). I guess this would be the least preferred option.

    Well there is one more thing, not really a solution, but see if you can get away with Factory.build in your specs, instead of actually creating the object. (Would be the simplest if you can).

    0 讨论(0)
  • 2020-12-12 12:51

    None of these solutions are good. They deface the class by removing functionality that should be removed from the instance, not from the class.

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

    Instead of suppressing the callback, I am suppressing the functionality of the callback. In a way, I like this approach better because it is more explicit.

    0 讨论(0)
  • 2020-12-12 12:51

    This solution works for me and you don´t have to add an additional block to your Factory definition:

    user = FactoryGirl.build(:user)
    user.send(:create_without_callbacks) # Skip callback
    
    user = FactoryGirl.create(:user)     # Execute callbacks
    
    0 讨论(0)
  • 2020-12-12 12:53

    I found the following solution to be a cleaner way since the callback is run/set at a class level.

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

    You could just set the callback with a trait for those instances when you want run it.

    0 讨论(0)
提交回复
热议问题