我正在测试一个带有后创建回调的模型,我想在测试时只在某些情况下运行。如何从工厂跳过/运行回调?
class User < ActiveRecord::Base
after_create :run_something
...
end
厂:
FactoryGirl.define do
factory :user do
first_name "Luiz"
last_name "Branco"
...
# skip callback
factory :with_run_something do
# run callback
end
end
我不确定它是否是最佳解决方案,但我已经成功实现了这一目标:
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
没有回调运行:
FactoryGirl.create(:user)
使用回调运行:
FactoryGirl.create(:user_with_run_something)
James Chevalier关于如何跳过before_validation回调的答案对我没有帮助,所以如果你像我这样摇摆不定就是工作解决方案:
在模型中:
before_validation :run_something, on: :create
在工厂:
after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) }
在我的情况下,我有回调加载到我的redis缓存。但后来我没有/想要为我的测试环境运行一个redis实例。
after_create :load_to_cache
def load_to_cache
Redis.load_to_cache
end
对于我的情况,与上面类似,我只是在我的spec_helper中存储了我的load_to_cache
方法,其中:
Redis.stub(:load_to_cache)
此外,在我想要测试它的某些情况下,我只需要在相应的Rspec测试用例的前一块中取消它们。
我知道你的after_create
可能会发生一些更复杂的事情,或者可能找不到这个非常优雅的东西。您可以尝试取消模型中定义的回调,方法是在工厂中定义after_create
挂钩(请参阅factory_girl文档),根据此处的“取消回调”部分,您可以在其中定义相同的回调并返回false
。 article。 (我不确定执行回调的顺序,这就是我没有选择此选项的原因)。
最后,(抱歉,我无法找到这篇文章)Ruby允许你使用一些脏的元编程解开一个回调钩子(你必须重置它)。我想这将是最不受欢迎的选择。
还有一件事,不是真正的解决方案,但看看你是否可以在规范中使用Factory.build,而不是实际创建对象。 (如果可以的话,这将是最简单的)。
关于上面发布的答案,https://stackoverflow.com/a/35562805/2001785,您不需要将代码添加到工厂。我发现在规范本身中重载方法更容易。例如,而不是(与引用文章中的工厂代码一起)
let(:user) { FactoryGirl.create(:user) }
我喜欢使用(没有引用的工厂代码)
let(:user) do
FactoryGirl.build(:user).tap do |u|
u.define_singleton_method(:send_welcome_email){}
u.save!
end
end
end
这样,您无需查看工厂和测试文件即可了解测试的行为。
skip_callback
raising Argument error when skipping from a FactoryBot factory.ArgumentError: After commit callback :whatever_callback has not been defined
有一个change in Rails 5,其中skip_callback如何处理无法识别的回调:
ActiveSupport :: Callbacks #skip_callback现在如果删除了无法识别的回调,则会引发ArgumentError
当从工厂调用skip_callback
时,AR模型中的实际回调尚未定义。
如果你已经尝试了所有东西并像我一样拉出你的头发,这是你的解决方案(got it from searching FactoryBot issues)(注意raise: false
部分):
after(:build) { YourSweetModel.skip_callback(:commit, :after, :whatever_callback, raise: false) }
随意使用您喜欢的任何其他策略。
我发现以下解决方案是一种更干净的方式,因为回调是在类级别运行/设置的。
# 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
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
您可以在需要运行时为这些实例设置回调。
如果您不想运行回调,请执行以下操作:
User.skip_callback(:create, :after, :run_something)
Factory.create(:user)
请注意,skip_callback在运行后将在其他空间中保持不变,因此请考虑以下内容:
before do
User.skip_callback(:create, :after, :run_something)
end
after do
User.set_callback(:create, :after, :run_something)
end
这些解决方案都不好。它们通过删除应该从实例中删除的功能来破坏类,而不是从类中删除。
factory :user do
before(:create){|user| user.define_singleton_method(:send_welcome_email){}}
我没有抑制回调,而是抑制了回调的功能。在某种程度上,我更喜欢这种方法,因为它更明确。
我想对@luizbranco的回答进行改进,以便在创建其他用户时使after_save回调更具可重用性。
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
没有after_save回调运行:
FactoryGirl.create(:user)
使用after_save回调运行:
FactoryGirl.create(:user, :with_after_save_callback)
在我的测试中,我更喜欢默认创建没有回调的用户,因为使用的方法会运行我在测试示例中通常不需要的额外内容。
---------- UPDATE ------------我停止使用skip_callback,因为测试套件中存在一些不一致的问题。
替代解决方案1(使用存根和取消存储):
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
替代解决方案2(我的首选方法):
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
此解决方案适用于我,您无需在Factory定义中添加其他块:
user = FactoryGirl.build(:user)
user.send(:create_without_callbacks) # Skip callback
user = FactoryGirl.create(:user) # Execute callbacks
在Rspec 3中,一个简单的存根最适合我
allow(User).to receive_messages(:run_something => nil)
从我的工厂调用skip_callback对我来说是个问题。
在我的例子中,我有一个文档类,在创建之前和之后都有一些与s3相关的回调,我只想在测试完整堆栈时运行。否则,我想跳过那些s3回调。
当我在我的工厂尝试skip_callbacks时,即使我直接创建了一个文档对象而没有使用工厂,它也会持续回调。所以相反,我在后构建调用中使用了mocha存根,一切都运行良好:
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
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
重要提示您应指定它们。如果仅使用之前并运行多个规范,它将尝试多次禁用回调。它将在第一次成功,但在第二次,回调将不再被定义。所以它会出错
这将适用于当前的rspec语法(截至本文),并且更加清晰:
before do
User.any_instance.stub :run_something
end