【问题标题】:Testing after_commit with RSpec and mocking使用 RSpec 和模拟测试 after_commit
【发布时间】:2014-05-24 05:09:05
【问题描述】:

我有一个模型主管和一个回调: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 回调的最佳方法是什么?

【问题讨论】:

    标签: ruby-on-rails ruby rspec mocking


    【解决方案1】:

    尝试使用test_after_commitgem

    或在 spec/support/helpers/test_after_commit.rb 中添加以下代码 - Gist

    【讨论】:

    • 你也可以在it块体的末尾使用subject.run_callbacks(:commit)
    • 这个 gem 非常适合 Rails 3 和 Rails 4。在 Rails 5 中,它不再需要,因为它已被添加到核心:github.com/rails/rails/pull/18458/commits/…
    【解决方案2】:

    我认为Mihail Davydenkov's comment应该是一个答案:

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

    另请注意,此问题(事务测试中未调用提交回调)应在 rails 5.0+ 中修复,因此您可能希望在升级期间删除任何可能使用的变通方法。见:https://github.com/rails/rails/pull/18458

    【讨论】:

    • 如果回调是用on: :update 定义的,我不能在4.2.7 上工作。在运行回调以使其工作之前,我还必须执行 subject.instance_variable_get(:@_start_transaction_state)[:new_record]=false
    • 虽然它执行回调但 self.previous_changes 是 { } 这不是真的。如何获得?
    【解决方案3】:

    我正在使用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_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
      

      【讨论】:

        【解决方案5】:

        我用这样的东西

        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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-02-10
          • 2013-08-04
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多