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)
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
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.
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
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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With