使用RSpec和模拟测试after_commit

34

我有一个模型Lead和一个回调函数:after_commit :create, :send_to_SPL

我使用的是Rails-4.1.0、ruby-2.1.1和RSpec。

1)这个测试未通过:

context 'callbacks' do
  it 'shall call \'send_to_SPL\' after create' do
    expect(lead).to receive(:send_to_SPL)
    lead = Lead.create(init_hash)
    p lead.new_record? # => false
  end
end

2) 这个规格也没有通过:

context 'callbacks' do
  it 'shall call \'send_to_SPL\' after create' do
    expect(ActiveSupport::Callbacks::Callback).to receive(:build)
    lead = Lead.create(init_hash)
  end
end

3)这个测试通过了,但我认为它没有在 after_commit 回调函数中进行测试:

context 'callbacks' do
  it 'shall call \'send_to_SPL\' after create' do
    expect(lead).to receive(:send_to_SPL)
    lead.send(:send_to_SPL)
  end
end

Rails中测试after_commit回调的最佳方法是什么?
5个回答

60

我认为Mihail Davydenkov的评论值得成为一个答案:

您还可以使用subject.run_callbacks(:commit)

另外请注意,这个问题(事务测试中未调用提交回调)应该在Rails 5.0+中得到解决,因此当您升级时,您可能希望记下删除任何在此期间使用的解决方法。参见:https://github.com/rails/rails/pull/18458


1
如果回调函数使用 on: :update 定义,则无法在 4.2.7 上工作。在运行回调之前,我还需要执行 subject.instance_variable_get(:@_start_transaction_state)[:new_record]=false 以使其正常工作。 - Dmitriy Budnik
尽管它执行了回调函数,但self.previous_changes为空,这是不正确的。如何获取它呢? - Imran Ahmad

40

尝试使用test_after_commit gem或在spec/support/helpers/test_after_commit.rb中添加以下代码 - Gist


38
it 块的末尾使用 subject.run_callbacks(:commit)。请注意不要修改原意,使翻译内容更易懂。 - Mihail Davydenkov
2
这个 gem 在 Rails 3 和 Rails 4 上运行得非常好。在 Rails 5 中,它已经被添加到核心中,因此不再需要使用这个 gem:https://github.com/rails/rails/pull/18458/commits/eb72e349b205c47a64faa5d6fe9f831aa7fdddf3 - Joshua Pinter

12
我正在使用DatabaseCleaner,并进行了配置以便我可以轻松地在事务和截断之间切换,其中前者由于速度而更受欢迎,但后者在测试回调时也可以使用。
RSpec的beforeafter处理程序使用作用域,因此如果您想将截断作为一个作用域,请定义一个before处理程序;
config.before(:each, truncate: true) do
  DatabaseCleaner.strategy = :truncation
end

现在要在describecontextit代码块中使用此配置,您应将其声明为:

describe "callbacks", truncate: true do
   # all specs within this block will be using the truncation strategy
  describe "#save" do
    it "should trigger my callback" do
      expect(lead).to receive(:send_to_SPL)
      lead = Lead.create(init_hash)
    end
  end
end

完整的钩子配置:(存储在 spec/support/database_cleaner.rb 中)

RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each, truncate: true) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.append_after(:each) do
    DatabaseCleaner.clean
  end
end

4

针对Rails5的更新。

回调处理确实已修复,但您可能仍需要频繁使用#reload

例如:
给定一个定义了after-create回调的模型:

after_create_commit { assign_some_association }

您可以使用以下方式指定此行为:

describe "callbacks" do
  describe "assigning_some_association" do
    subject(:saving) { record.save!; record.reload } # reload here is important

    let(:record) { build(:record) }

    it "assigns some association after commit" do        
      expect{ saving }.to(
        change{ record.some_association_id }.from(nil).to(anything)
      )
    end
  end
end

0

我使用类似这样的东西

describe 'some method on record' do
  let(:record) { create(:some_record) }
  let(:update_block) { ->(record) { record.save! } } # define an labmda that will be called in a transaction block
  let(:result_method) { :some_method } # define a method to be called
  let(:result) do
    record.class_eval <<~EVAL, __FILE__, __LINE__ + 1
      after_commit :_record_result
       def _record_result
        @_result = public_send(:#{result_method})
      end
    EVAL

    record.transaction do
      update_block.call(record)
    end

    record.instance_variable_get(:'@_result')
  end

  before do
    # apply changes to record
  end

  it 'returns the correct result' do
    expect(result).to eq(some_value)
  end
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接