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?
class User < ActiveRecord::Base
after_create :run_something
...
end
Factory:
FactoryGirl.define do
factory :user do
first_name "Luiz"
last_name "Branco"
...
# skip callback
factory :with_run_something do
# run callback
end
end
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){}}
end
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
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.
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
A simple stub worked best for me in Rspec 3
allow_any_instance_of(User).to receive_messages(:run_something => nil)
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
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
This will work with current rspec syntax (as of this post) and is much cleaner:
before do
User.any_instance.stub :run_something
end
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) }
This is an older question, with some good answers, but none of them quite worked for me for a few reasons
didn't like the idea of modifying the behavior of some class at runtime
didn't want to use attr_accessor all throughout my classes because it seemed weird to put logic only used for tests inside models
didn't want to put a call to rspec before/after blocks on various specs to stub/unstub behavior
using FactoryBot you can use transient in your factory to set a switch to modify behavior of your classes. As a result, factories/specs look like
#factory
FactoryBot.define do
factory :user do
transient do
skip_after_callbacks { true }
end
after(:build) do |user, evaluator|
if evaluator.skip_after_callbacks
class << user
def callback_method1; true; end
def callback_method2; true; end
def callback_method3; true; end
end
end
end
end
end
# without running callbacks
user = create(:user)
# with running callbacks for certain specs
user = create(:user, skip_after_callbacks: false)
This worked for me because our app has certain methods that are triggered as a result of various after_create/after_commit callbacks that run to external services, so by default I don't typically need those to run in specs. Doing this saved our test suite on various calls using VCR. YMMV
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).
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
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.
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])
BONUS
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
I had a familiar problem that i wanted to skip callbacks only when i create a record from FactoryBot and answers posted here did not solve my problem so i found my own solution, i'm posting it here so may be it will be useful for someone else.
Class
class User < ApplicationRecord
before_save :verify
end
Factory
FactoryBot.define do
factory :user do
transient do
skip_verify_callback { true }
end
before(:create) do |user, evaluator|
user.class.skip_callback(:save, :before, :verify) if evaluator.skip_verify_callback
end
after(:create) do |user, evaluator|
user.class.set_callback(:save, :before, :verify) if evaluator.skip_verify_callback
end
end
end
NOTE: Above create callbacks runs after only FactoryBot.create, so FactoryBot.build will not trigger these.
I set the default behavior of the factory to skip the verify callback while i still have the ability to prevent this by creating user with a argument like this:
FactoryBot.create(:user, skip_verify_callback: false)
I think this approach safer because FactoryBot.create starts and ends in instant and we won't have any side effects of skipping callbacks.
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.
Related
I’m using Rails 4.2.3 with FactoryGirl. I have this factory for my users
FactoryGirl.define do
…
factory :user do
after(:build) do |user, vars|
print "in main user after build.\n"
def user.publish
# and here you can stub method response if you need
end
end
…
trait :with_callbacks do
after(:build) do |user, vars|
print "after build my user\n"
end
…
end
I wanted to override my base “after(:build)” method, so I created the trait “with_callbacks.” But when I call my factory with my traits
create(:user, :my_user)
It seems like both “after(:build)” methods are getting called based on the output …
after build my user
in main user after build.
Is there a way to rig things so that I can override the base factory’s “after(:build)” method?
Not sure what you're trying to do, but another option would be to use a transient variable (called ignore in FactoryBot versions < 5.0) to control the behavior, which can be overwritten in a trait.
Something like:
transient do # use `ignore` in factory bot < 5.0
with_callback_behavior { false }
end
after(:build) do |user, vars|
if vars.with_callback_behavior
puts "behavior for trait after build."
else
puts "behavior for main main after build."
end
end
trait :with_callbacks do
with_callback_behavior { true }
end
Of course, this means that
build :user, :with_callbacks
#=> "behavior for trait after build."
is the same as
build :user, with_callback_behavior: true
#=> "behavior for trait after build."
I have some models that use an after_initialize hook like so to set a load of default attributes and other things:
after_initialize do |some_model|
initializer(some_model)
end
def initializer(some_model)
#... loads of pre-processing
end
This all runs great in development and production, however in my FactoryBot and rspec tests this hook isn't running. I have a factory that looks like this:
FactoryBot.define do
factory :some_model do
some_number { 1 }
is_it_something { false }
account { create(:user).accounts.first }
end
end
But my tests are looking like this:
some_model = create(:some_model)
some_model.initializer(some_model)
The issue is I'm having to call the initializer seperatly after creating the FactoryBot model. This is causing all sorts of issues because the create() triggers the create validations, and they fail because the initializer isn't ran.
How can I get FactoryBot to run after_initialize when creating a new instance?
Thanks.
(I might have got some terminology wrong here, so please correct me if I have.)
You could use the after(:build) callback of the factory to call the initializer since factorybot sets the attributes after initialization.
FactoryBot.define do
factory :some_model do
some_number { 1 }
is_it_something { false }
account { create(:user).accounts.first }
after(:build) do |some_model|
some_model.initializer(some_model)
end
end
end
that way you don't have to call it every time you create new object from the factory
Anyway, this initializer method looks like a code smell. I'm not sure what does it do, but maybe there's a better way to do that that also prevents this problem.
FactoryBot calls setters for attributes, not the constructor, so build :some_model, some_attribute: 123, other_attribute: :foo is in fact equivalent of:
SomeModel.new.tap{|m|
m.some_attribute = 123
m.other_attribute = :foo
}
This way initializer is called on a empty object and only after that all other attributes are getting set.
When you need to set some attributes that are dependent on others - i'd opt into custom setters like
def some_other_attribute=(val)
self.some_attribute = calculate(val)
super
end
or do it in before_validation
I would like to create an Active Record model for Rspec test.
However, this model has callbacks, namely: before_create and after_create method (I think these methods are called callbacks not validation if I am not wrong).
Is there a way to create an object without triggering the callbacks?
Some previous solutions I have tried / does not work for my case:
Update Method:
update_column and other update methods will not work because I would like to create an object and I can't use update methods when the object does not exist.
Factory Girl and After Build:
FactoryGirl.define do
factory :withdrawal_request, class: 'WithdrawalRequest' do
...
after(:build) { WithdrawalRequest.class.skip_callback(:before_create) }
end
end
Failure/Error: after(:build) { WithdrawalRequest.class.skip_callback(:before_create) }
NoMethodError:
undefined method `skip_callback' for Class:Class
Skip callbacks on Factory Girl and Rspec
Skip Callback
WithdrawalRequest.skip_callback(:before_create)
withdrawal_request = WithdrawalRequest.create(withdrawal_params)
WithdrawalRequest.set_callback(:before_create)
Failure/Error: WithdrawalRequest.skip_callback(:before_create)
NoMethodError:undefined method `_before_create_callbacks' for #
How to save a model without running callbacks in Rails
I have also tried
WithdrawalRequest.skip_callbacks = true
Which does not work too.
---------- EDIT -----------
My factory function is edited to:
after(:build) { WithdrawalRequest.skip_callback(:create, :before, :before_create) }
My before_create function looks like this:
class WithdrawalRequest < ActiveRecord::Base
...
before_create do
...
end
end
---------- EDIT 2 -----------
I changed the before_create to a function so that I can reference it. Is either of these a better practice?
class WithdrawalRequest < ActiveRecord::Base
before_create :before_create
...
def before_create
...
end
end
Based on the referenced answer:
FactoryGirl.define do
factory :withdrawal_request, class: 'WithdrawalRequest' do
...
after(:build) { WithdrawalRequest.skip_callback(:create, :before, :callback_to_be_skipped) }
#you were getting the errors here initially because you were calling the method on Class, the superclass of WithdrawalRequest
#OR
after(:build) {|withdrawal_request| withdrawal_request.class.skip_callback(:create, :before, :callback_to_be_skipped)}
end
end
Almost every spec file I come accross I end up writing stuff like:
before :each do
#cimg = Factory.build :cimg_valid
#cimg.stub(:validate_img).and_return true
#cimg.stub(:validate_img_url).and_return true
#cimg.stub(:save_images).and_return true
#cimg.stub(:process_image).and_return true
#cimg.stub(:img).and_return true
end
I mean, the model I get from Factory.build is completely valid. But if I don't stub that stuff it saves things in the filesystem, and validates stuff I'm not testing...
What I mean, I think it would be cleaner to do something like this:
before :each do
#cimg = Factory.build :cimg_for_testing_tags
end
If stubbing within the Factory is even possible.
What is the proper way to stub the model?
#fkreusch's answer works great until you use the new RSpec expect() syntax (3.0+)
Putting this into rails_helper.rb works for me:
FactoryBot::SyntaxRunner.class_eval do
include RSpec::Mocks::ExampleMethods
end
In the OP's example, you can now do:
FactoryBot.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after(:build) do |cimg|
allow(cimg).to receive(:validate_img) { true }
end
end
end
Credit: github.com/printercu, see: https://github.com/thoughtbot/factory_bot/issues/703#issuecomment-83960003
In recent versions of factory_girl you have an after_build callback, so I believe you could define your factory like this:
FactoryGirl.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after_build do |cimg|
cimg.stub(:validate_img).and_return true
end
end
end
UPDATE
After factory_girl 3.3.0, the syntax has changed to following:
FactoryGirl.define do
factory :cimg_for_testing_tags do
... # Factory attributes
after(:build) do |cimg|
cimg.stub(:validate_img).and_return true
end
end
end
A factory should produce "real world" objects therefore it's a bad practice (and error prone) to change behaviour (i.e. stub) in a factory.
You can do
let(:user) instance_double(User, FactoryGirl.attributes_for(:user))
before do
allow(user).to receive(:something).and_return('something')
end
and if your before clause gets too big you may want to extract it to a separate method or create a mock child class that overrides methods you want to stub.
You might also consider using FactoryGirl#build_stubbed.
Suppose you have an ActiveRecord::Observer in one of your Ruby on Rails applications - how do you test this observer with rSpec?
You are on the right track, but I have run into a number of frustrating unexpected message errors when using rSpec, observers, and mock objects. When I am spec testing my model, I don't want to have to handle observer behavior in my message expectations.
In your example, there isn't a really good way to spec "set_status" on the model without knowledge of what the observer is going to do to it.
Therefore, I like to use the "No Peeping Toms" plugin. Given your code above and using the No Peeping Toms plugin, I would spec the model like this:
describe Person do
it "should set status correctly" do
#p = Person.new(:status => "foo")
#p.set_status("bar")
#p.save
#p.status.should eql("bar")
end
end
You can spec your model code without having to worry that there is an observer out there that is going to come in and clobber your value. You'd spec that separately in the person_observer_spec like this:
describe PersonObserver do
it "should clobber the status field" do
#p = mock_model(Person, :status => "foo")
#obs = PersonObserver.instance
#p.should_receive(:set_status).with("aha!")
#obs.after_save
end
end
If you REALLY REALLY want to test the coupled Model and Observer class, you can do it like this:
describe Person do
it "should register a status change with the person observer turned on" do
Person.with_observers(:person_observer) do
lambda { #p = Person.new; #p.save }.should change(#p, :status).to("aha!)
end
end
end
99% of the time, I'd rather spec test with the observers turned off. It's just easier that way.
Disclaimer: I've never actually done this on a production site, but it looks like a reasonable way would be to use mock objects, should_receive and friends, and invoke methods on the observer directly
Given the following model and observer:
class Person < ActiveRecord::Base
def set_status( new_status )
# do whatever
end
end
class PersonObserver < ActiveRecord::Observer
def after_save(person)
person.set_status("aha!")
end
end
I would write a spec like this (I ran it, and it passes)
describe PersonObserver do
before :each do
#person = stub_model(Person)
#observer = PersonObserver.instance
end
it "should invoke after_save on the observed object" do
#person.should_receive(:set_status).with("aha!")
#observer.after_save(#person)
end
end
no_peeping_toms is now a gem and can be found here: https://github.com/patmaddox/no-peeping-toms
If you want to test that the observer observes the correct model and receives the notification as expected, here is an example using RR.
your_model.rb:
class YourModel < ActiveRecord::Base
...
end
your_model_observer.rb:
class YourModelObserver < ActiveRecord::Observer
def after_create
...
end
def custom_notification
...
end
end
your_model_observer_spec.rb:
before do
#observer = YourModelObserver.instance
#model = YourModel.new
end
it "acts on the after_create notification"
mock(#observer).after_create(#model)
#model.save!
end
it "acts on the custom notification"
mock(#observer).custom_notification(#model)
#model.send(:notify, :custom_notification)
end