Ruby-on-rails 跳过 Factory Girl 和 Rspec 的回调

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8751175/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 02:34:56  来源:igfitidea点击:

Skip callbacks on Factory Girl and Rspec

ruby-on-railsrspecfactory-bot

提问by luizbranco

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

回答by luizbranco

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)

回答by Minimul

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:

请注意,skip_callback 在运行后将在其他规范中保持不变,因此请考虑以下内容:

before do
  User.skip_callback(:create, :after, :run_something)
end

after do
  User.set_callback(:create, :after, :run_something)
end

回答by B Seven

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.

我不是抑制回调,而是抑制回调的功能。在某种程度上,我更喜欢这种方法,因为它更明确。

回答by konyak

I'd like to make an improvement to @luizbranco 's answer to make after_save callback more reusable when creating other users.

我想对 @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

Running without after_save callback:

在没有 after_save 回调的情况下运行:

FactoryGirl.create(:user)

Running with after_save callback:

使用 after_save 回调运行:

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.

----------更新------------ 我停止使用skip_callback,因为测试套件中存在一些不一致的问题。

Alternative Solution 1 (use of stub and unstub):

替代解决方案 1(使用 stub 和 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):

替代解决方案 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

回答by RudyOnRails

Rails 5 - skip_callbackraising Argument error when skipping from a FactoryBot factory.

Rails 5 -skip_callback从 FactoryBot 工厂跳过时引发参数错误。

ArgumentError: After commit callback :whatever_callback has not been defined

There was a change in Rails 5with how skip_callback handles unrecognized callbacks:

Rails 5 中的skip_callback 如何处理无法识别的回调发生变化

ActiveSupport::Callbacks#skip_callback now raises an ArgumentError if an unrecognized callback is remove

如果删除无法识别的回调,ActiveSupport::Callbacks#skip_callback 现在会引发 ArgumentError

When skip_callbackis called from the factory, the real callback in the AR model is not yet defined.

skip_callback从工厂调用时,AR 模型中真正的回调尚未定义。

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: falsepart):

如果您已经尝试了所有方法并像我一样将头发拔掉,那么这是您的解决方案(通过搜索 FactoryBot 问题获得)注意raise: false部分):

after(:build) { YourSweetModel.skip_callback(:commit, :after, :whatever_callback, raise: false) }

Feel free to use it with whatever other strategies you prefer.

随意将它与您喜欢的任何其他策略一起使用。

回答by auralbee

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

回答by AndreiMotinga

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

重要说明您应该同时指定它们。如果只使用 before 并运行多个规范,它会尝试多次禁用回调。第一次会成功,但第二次,回调将不再被定义。所以它会出错

回答by samg

A simple stub worked best for me in Rspec 3

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

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

回答by uberllama

Calling skip_callback from my factory proved problematic for me.

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

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.

就我而言,我有一个文档类,在创建之前和之后有一些与 s3 相关的回调,我只想在需要测试完整堆栈时运行。否则,我想跳过那些 s3 回调。

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:

当我在我的工厂中尝试 skip_callbacks 时,即使我直接创建了一个文档对象,它也坚持了回调跳过,而不使用工厂。因此,我在 after build 调用中使用了 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

回答by Zyren

This will work with current rspec syntax (as of this post) and is much cleaner:

这将适用于当前的 rspec 语法(截至本文)并且更清晰:

before do
   User.any_instance.stub :run_something
end