跳过Factory Girl和Rspec的callback

我正在使用after createcallback来testing一个模型,我只想在testing的时候在某些场合运行。 我怎样才能跳过/运行工厂callback?

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 

我不确定这是否是最好的解决scheme,但是我已经成功地实现了这一点:

 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 

无callback运行:

 FactoryGirl.create(:user) 

以callback运行:

 FactoryGirl.create(:user_with_run_something) 

当你不想运行callback时,请执行以下操作:

 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 

我想对@luizbranco的回答作出改进,以便在创build其他用户时使after_savecallback更加可重用。

 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_savecallback运行:

 FactoryGirl.create(:user) 

使用after_savecallback运行:

 FactoryGirl.create(:user, :with_after_save_callback) 

在我的testing中,我更喜欢在没有默认callback的情况下创build用户,因为所使用的方法会运行额外的东西,我通常不需要在我的testing示例中。

———- UPDATE ————我停止使用skip_callback,因为在testing套件中有一些不一致的问题。

替代解决scheme1(使用存根和未读):

 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 

替代解决scheme2(我的首选方法):

 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 

这些解决scheme都不是很好。 他们通过删除应该从实例中删除的function,而不是从类中删除的function来污损class级。

 factory :user do before(:create){|user| user.define_singleton_method(:send_welcome_email){}} 

而不是抑制callback,我压制callback的function。 在某种程度上,我更喜欢这种方法,因为它更加明确。

从我的工厂调用skip_callbackcertificate对我来说是有问题的。

在我的情况下,我有一个文件类与一些s3相关的callback在创build之前和之后,我只想运行时testing完整的堆栈是必要的。 否则,我想跳过这些S3callback。

当我在我的工厂尝试skip_callbacks时,即使我直接创build了一个文档对象,也没有使用工厂,它仍然保留了callback跳过。 所以相反,我使用摩卡桩在后build立呼叫,一切都工作完美:

 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 

这将使用目前的rspec语法(截至这篇文章),是更清洁:

 before do User.any_instance.stub :run_something end 

一个简单的存根在Rspec 3中最适合我

 allow(User).to receive_messages(:run_something => nil) 

这个解决scheme适用于我,而且您不必为Factory定义添加额外的块:

 user = FactoryGirl.build(:user) user.send(:create_without_callbacks) # Skip callback user = FactoryGirl.create(:user) # Execute callbacks 

在我的情况下,我有callback加载到我的rediscaching。 但是,我没有/想要为我的testing环境运行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) 

此外,在某些情况下,我想要testing这个,我只需要在相应的Rspectesting用例的前面块中取消它们。

我知道你可能有更复杂的事情发生在你的after_create或者可能不会觉得这非常优雅。 您可以尝试取消您的模型中定义的callback,方法是在Factory中定义一个after_create挂钩(请参阅after_create文档),您可以根据此处的“取消callback”部分定义相同的callback并返回false 文章 。 (我不确定执行callback的顺序,这就是为什么我没有去这个选项)。

最后,(对不起,我找不到这篇文章)Ruby允许你使用一些脏的元编程来解除一个callback钩子(你将不得不重置它)。 我想这将是最不推荐的select。

那么还有一件事情,并不是一个真正的解决scheme,但是看看你能否在规范中脱离Factory.build,而不是实际创build对象。 (如果可以的话,会是最简单的)。

James Chevalier关于如何跳过before_validationcallback的答案并没有帮助我,所以如果你像我一样徘徊在这里工作的解决scheme:

在模型中:

 before_validation :run_something, on: :create 

在工厂:

 after(:build) { |obj| obj.class.skip_callback(:validation, :before, :run_something) } 
 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 

重要提示你应该指定他们两个。 如果只使用之前和运行多个规格,它会尝试多次禁用callback。 第一次会成功,但第二次,callback不会被定义。 所以会出错

 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 

当你想运行这些实例时,你可以设置callback的特性。