【问题标题】:Rails 3: ActiveRecord observer: after_commit callback doesn't fire during tests, but after_save does fireRails 3:ActiveRecord 观察者:在测试期间不会触发 after_commit 回调,但会触发 after_save
【发布时间】:2015-01-24 04:22:49
【问题描述】:

我有一个 Rails 3 应用程序。我对某些模型使用 after_save 回调,对其中一个模型使用 after_commit 回调。代码中的所有代码都可以正常工作,但是在 RSpec 测试期间,当我保存 Thing 模型时,不会调用 after_commit 回调。

例如

class ThingObserver  <  ActiveRecord:Observer
  observe Thing
  def after_commit(thing)
    puts thing.inspect
  end
end

如果我将方法名称更改为after_save,它会在测试期间正常调用。我需要能够将after_commit 用于此特定模型,因为在某些情况下,对“事物”的更改发生在 Web 服务器中,但观察者的影响发生在 Sidekiq 工作人员中,而 after_save 不会保证数据在工作人员准备好时已提交并可用。

RSpec 的配置在 spec/spec_helper.rb 中如下所示

Rspec.configure do |config|
  #yada yada
  config.use_transactional_fixtures = true
  #yada yada
end

我还调整了 rake db:create 以便它从 structure.sql 文件中提取。在 lib/tasks/db.rb 中

task setup: [ 'test:ensure_environment_is_test', 'db:create', 'db:structure:load', 'db:migrate', 'db:seed' ]

我这样做是为了运行测试以确保数据库执行外键约束。

有没有办法同时运行 after_save 和 after_commit 回调而不使 Rspec use_transactional_fixtures == false?

或者,有没有办法将 config.use_transactional_fixtures 设置为 'false' 仅用于该测试或该测试文件?

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-3 rspec observers


    【解决方案1】:

    要正确执行数据库提交,您需要启用 config.use_transactional_fixtures,但我建议您考虑不同的策略,因为为了良好的测试设计,默认情况下禁用该选项,以强制您的测试是单一和孤立的尽可能。

    首先,您可以使用 #run_callbacks(type) 运行 ActiveRecord 回调,在您的情况下为 model.run_callbacks(:commit)

    我的首选策略是拥有一个具有您要运行的逻辑的方法,然后使用方法名称声明钩子,然后通过直接调用它来测试方法行为,并测试在钩子运行时调用该方法。

    class Person
      after_commit :register_birth
    
      def register_birth
        # your code
      end
    end
    
    describe Person do
      describe "registering birth" do
        it "registers ..." do
        end
    
        it "runs after database insertion" do
          expect(model).to receive(:register_birth)
          model.run_callbacks(:commit)
        end
      end
    end
    

    这假定您在回调上的任何逻辑对于模型状态都不是必需的,即不会将其更改为您需要立即使用的东西,并且与它交互的任何其他模型都对其无动于衷。因此不需要在测试上下文中运行。这是一个强大的设计原则,从长远来看,它通过要求设置一些与您正在测试的单元无关的属性仅在您不关心的回调上使用来防止回调失控并为测试生成依赖项大约在那个时候。

    但是,最终你比陌生人更了解你的领域和设计要求,所以如果你真的需要 after_commit 来运行,你可以用 model.run_callbacks(:commit) 强制它。只需将其封装在您的工厂/夹具上,您不必每次都记住它。

    【讨论】:

    • 不确定是否有人会看到这个,但我想知道是否有人知道如果 run_callbacks(:commit) 不做任何事情意味着什么,例如此方法调用的 test.log 为空?
    • 你这是什么意思?您是否在回调方法中记录任何内容?还是on: :update? @本
    • 我遇到了一个问题,我的模型没有提交到数据库(原来是数据库清理器限制了这一点)。所以我尝试 run_Callback(:commit) 以确保模型正在提交。但什么也没发生?
    • 回调不提交,只运行after/before_commit设置的钩子
    猜你喜欢
    • 2018-03-11
    • 2016-06-30
    • 2020-08-09
    • 2016-02-07
    • 1970-01-01
    • 2015-04-18
    • 2012-11-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多