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
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
A simple stub worked best for me in Rspec 3
allow_any_instance_of(User).to receive_messages(:run_something => nil)
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
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])
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