跳过Factory Girl和Rspec的回调

lui*_*nco 97 rspec ruby-on-rails factory-bot

我正在测试一个带有后创建回调的模型,我想在测试时只在某些情况下运行.如何从工厂跳过/运行回调?

class User < ActiveRecord::Base
  after_create :run_something
  ...
end
Run Code Online (Sandbox Code Playgroud)

厂:

FactoryGirl.define do
  factory :user do
    first_name "Luiz"
    last_name "Branco"
    ...
    # skip callback

    factory :with_run_something do
      # run callback
  end
end
Run Code Online (Sandbox Code Playgroud)

lui*_*nco 107

我不确定它是否是最佳解决方案,但我已经成功实现了这一目标:

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
Run Code Online (Sandbox Code Playgroud)

没有回调运行:

FactoryGirl.create(:user)
Run Code Online (Sandbox Code Playgroud)

使用回调运行:

FactoryGirl.create(:user_with_run_something)
Run Code Online (Sandbox Code Playgroud)

  • 反转跳过回调逻辑不是更好吗?我的意思是,默认应该是当我创建一个对象时触发回调,并且我应该为异常情况使用不同的参数.所以FactoryGirl.create(:user)应该创建触发回调的用户,而FactoryGirl.create(:user_without_callbacks)应该创建没有回调的用户.我知道这只是一个"设计"修改,但我认为这可以避免破坏现有的代码,并且更加一致. (7认同)
  • 如果你想跳过`:on =>:create`验证,请使用`after(:build){| user | user.class.skip_callback(:validate,:create,:after,:run_something)}` (3认同)
  • 正如@ Minimal的解决方案所指出的那样,`Class.skip_callback`调用将在其他测试中保持不变,因此如果您的其他测试期望回调发生,如果您尝试反转跳过回调逻辑,它们将失败. (3认同)

Min*_*mul 81

如果您不想运行回调,请执行以下操作:

User.skip_callback(:create, :after, :run_something)
Factory.create(:user)
Run Code Online (Sandbox Code Playgroud)

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

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

after do
  User.set_callback(:create, :after, :run_something)
end
Run Code Online (Sandbox Code Playgroud)

  • 我更喜欢这个答案,因为它明确指出跳过回调在类级别上挂起,因此会在后续测试中继续跳过回调. (11认同)

B S*_*ven 34

这些解决方案都不好.它们通过删除应该从实例中删除的功能来破坏类,而不是从类中删除.

factory :user do
  before(:create){|user| user.define_singleton_method(:send_welcome_email){}}
Run Code Online (Sandbox Code Playgroud)

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

  • 我真的很喜欢这个答案,并且想知道像这样的别名是否应该成为 FactoryGirl 本身的一部分,以便意图立即清晰。 (2认同)

kon*_*yak 25

我想对@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
Run Code Online (Sandbox Code Playgroud)

没有after_save回调运行:

FactoryGirl.create(:user)
Run Code Online (Sandbox Code Playgroud)

使用after_save回调运行:

FactoryGirl.create(:user, :with_after_save_callback)
Run Code Online (Sandbox Code Playgroud)

在我的测试中,我更喜欢默认情况下创建没有回调的用户,因为使用的方法会运行我在测试示例中通常不需要的额外内容.

---------- 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
Run Code Online (Sandbox Code Playgroud)

替代解决方案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
Run Code Online (Sandbox Code Playgroud)


aur*_*bee 6

此解决方案适用于我,您无需在Factory定义中添加其他块:

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

user = FactoryGirl.create(:user)     # Execute callbacks
Run Code Online (Sandbox Code Playgroud)


Rud*_*ils 6

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

ArgumentError: After commit callback :whatever_callback has not been defined
Run Code Online (Sandbox Code Playgroud)

Rails 5中有一个更改,其中skip_callback如何处理无法识别的回调:

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

skip_callback从工厂调用,在AR模型中的实际回调尚未确定。

如果您已尝试了所有步骤并像我一样拉了头发,这是您的解决方案(可通过搜索FactoryBot问题来解决)(请注意raise: false部分):

after(:build) { YourSweetModel.skip_callback(:commit, :after, :whatever_callback, raise: false) }
Run Code Online (Sandbox Code Playgroud)

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


ube*_*ama 5

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

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

当我在工厂中尝试skip_callbacks时,即使我直接创建文档对象而不使用工厂,它也会保留回调跳过。因此,我在构建后调用中使用了摩卡存根,一切都运行良好:

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
Run Code Online (Sandbox Code Playgroud)


sam*_*amg 5

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

allow(User).to receive_messages(:run_something => nil)
Run Code Online (Sandbox Code Playgroud)

  • 你需要为`User`的_instances_设置它; `:run_something`不是类方法. (4认同)

And*_*nga 5

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
Run Code Online (Sandbox Code Playgroud)

重要说明, 您应同时指定两者。如果仅在之前使用并运行多个规范,它将尝试多次禁用回调。它将第一次成功,但是第二次将不再定义回调。所以会出错