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
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