如何使用 Rspec 和 FactoryGirl 在 rails 模型中测试 before_update 回调?

How to test before_update callback in rails model with Rspec and FactoryGirl?

我正在尝试测试下面模型的 before_update 回调。

models/option.rb:

class Option < ApplicationRecord
  belongs_to :activity

  has_many :suboptions, class_name: "Option", foreign_key: "option_id"

  belongs_to :parent, class_name: "Option", optional: true, foreign_key: "option_id"

  accepts_nested_attributes_for :suboptions, allow_destroy: true,
    reject_if: ->(attrs) { attrs['name'].blank? }

  validates :name, presence: true

  before_create :set_defaults
  before_update :set_updates


  def set_defaults
    self.suboptions.each do |sbp|
      sbp.activity_id = self.activity_id
    end
  end

  def set_updates
    suboptions.each do |child|
      child.activity_id = self.activity_id
    end
  end
end

规格/models/option.rb:

require 'rails_helper'

RSpec.describe Option, type: :model do

  describe "Callbacks" do
    it "before_create" do
      suboption = create(:option)
      option = create(:option, suboptions:[suboption])
      option.run_callbacks(:create) {false}
      expect(option.suboptions.first.activity_id).to eq suboption.activity_id
    end

    it "before_update" do

    end
  end



end

我成功地测试了 before_create 回调(至少它给了我正确的结果)。但是我不知道如何测试 before_update 回调。有办法吗?

好的。我会尝试从头开始。

要测试回调,您必须测试它是否会在应该调用的时候调用。就这样。

您可能想要准确测试该方法的代码。但是这样的方法通常是私有的,而且它们确实应该是私有的。而且您根本不应该测试私有方法的代码。如果您无论如何都想这样做,您的测试将与您的私有方法耦合,这并不好。

您可以这样测试 before_update :set_updates

let(:option) { Option.create("init your params here") }

it "test callback" do
  expect(option).to receive(:set_updates)
  option.save
end

如果你想测试你的私有方法的代码,你可以这样做

let(:option) { Option.create("init your params here") }

it "test callback" do
  # expect to receive some messages
  # which are in your method code
  # for example
  expect_any_instance_of(Suboption).to receive(:activity_id=)
  option.send(:set_updates)
end

P.S. 你可能想要 watch/listen "Rails Conf 2013 The Magic Tricks of Testing by Sandi Metz"。很有帮助。

警告:这个答案是自以为是的。

测试行为,而不是实施。

回调是一个实现细节。不要直接测试它。相反,假装您不知道模型的内部工作原理,并测试它的行为方式。

如果我没看错代码,行为可以这样描述:

When updating an option, the activity_id of each of its suboptions is set to the activity_id of the option.

创建一个带有子选项的选项。更新它,重新加载它,并检查每个 activity_id 的值是否正确。

这会比模拟慢,但不那么脆弱。此外,测试更容易编写和维护。

我使用 run_callbacks 找到了解决方案。我创建了一个选项和一个子选项。然后我更新了选项的 activity_id 并使用 option.run_callbacks(:update) {false} 到 运行 before_update 回调(如果我使用 {true},它会 运行 before_updateafter_update 回调):

it "before_update" do
    suboption = create(:option)
    option = create(:option, suboptions:[suboption])
    option.update(activity_id: 5)
    option.run_callbacks(:update) {false}
    expect(option.suboptions.first.activity_id).to eq option.activity_id
end

如果我不使用 option.run_callbacks(:update) {false},expect 表达式会为选项和子选项获得不同的 activity_id。但是通过使用这样的代码,测试 运行s 正确并且选项和子选项具有相同的 activity_id.