简体   繁体   English

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

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

I am trying to test the before_update callback of the model bellow. 我试图测试下面的模型的before_update回调。

models/option.rb: 车型/ 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

spec/models/option.rb: 规格/型号/ 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

I successfully tested the before_create callback (at least it gave me the correct result). 我成功测试了before_create回调(至少它给了我正确的结果)。 But I don't know how to test the before_update callback. 但我不知道如何测试before_update回调。 Is there a way to do it? 有办法吗?

Warning: this answer is opinionated. 警告:这个答案是固执己见的。

Test behavior, not implementation. 测试行为,而不是实现。

A callback is an implementation detail. 回调是一个实现细节。 Don't test it directly. 不要直接测试。 Instead, pretend that you don't know how the model works internally, and test how it behaves. 相反,假装您不知道模型如何在内部工作,并测试它的行为方式。

If I'm reading the code correctly, the behavior can be described like so: 如果我正确地读取代码,可以这样描述行为:

When updating an option, the activity_id of each of its suboptions is set to the activity_id of the option. 更新选项时,每个子选项的activity_id都设置为选项的activity_id。

Create an option with suboptions. 使用子选项创建选项。 Update it, reload it, and check that the value of each activity_id is correct. 更新它,重新加载它,并检查每个activity_id的值是否正确。

This will be slower than mocking, but less brittle. 这比嘲弄慢,但不那么脆弱。 Also, the test is much easier to write and maintain. 此外,测试更容易编写和维护。

Ok. 好。 I'll try to start from the beginning. 我会尝试从头开始。

To test callback you have to test that it would be called when it should. 要测试回调,你必须测试它应该被调用。 That's all. 就这样。

You may want to test the code of the method exactly. 您可能希望完全测试方法的代码。 But such methods usually are private and they should be private indeed. 但这种方法通常是私人的,它们应该是私密的。 And you shouldn't test private methods' code at all. 而且你根本不应该测试私有方法的代码。 If you want to do it anyway, your test will be coupled to your private methods and that's not good. 无论如何你想要这样做,你的测试将与你的私人方法相结合,这并不好。

You can test before_update :set_updates like this: 你可以像这样测试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

If you want to test the code of your private method, you can do that like this 如果要测试私有方法的代码,可以这样做

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

PS You may want to watch/listen "Rails Conf 2013 The Magic Tricks of Testing by Sandi Metz". PS你可能想看/听“Rails Conf 2013 Sandi Metz的魔术测试技巧”。 It's very helpful thing. 这是非常有帮助的事情。

I got a solution using run_callbacks . 我使用run_callbacks得到了一个解决方案。 I created an option and a suboption. 我创建了一个选项和一个子选项。 Then I updated the activity_id of the option and used option.run_callbacks(:update) {false} to run before_update callback (if I used {true} , it would run before_update and after_update 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

If I do not use the option.run_callbacks(:update) {false} , the expect expression gets a different activity_id for option and suboption. 如果我不使用option.run_callbacks(:update) {false} ,则expect表达式会为选项和子选项获取不同的activity_id。 But by using the code like that, the test runs correctly and option and suboption have the same activity_id. 但是通过使用这样的代码,测试运行正确,选项和子选项具有相同的activity_id。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM