Rails 3: ActiveRecord观察者:after_commit回调不会在测试期间触发,但after_save会触发

时间:2022-12-18 01:19:20

I have a Rails 3 application. I use after_save callbacks for some models and after_commit callbacks for one of the models. All of the code the code works fine, but during RSpec tests, the after_commit callback doesn't get called when I save a Thing model.

我有一个Rails 3应用程序。我对一些模型使用after_save回调,对其中一个模型使用after_commit回调。所有代码都运行良好,但是在RSpec测试期间,当我保存一个对象模型时,不会调用after_commit回调。

e.g.

如。

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

If I change the method name to after_save, it gets called fine during tests. I need to be able to use after_commit for this particular model because in some situations the change to a "thing" happens in a web server, but the effect of the observer happens in a Sidekiq worker, and after_save doesn't guarantee that the data was committed and available when the worker is ready for it.

如果我将方法名更改为after_save,那么在测试期间会调用fine。我需要能够使用after_commit对于这个特定的模式,因为在某些情况下改变到一个“事”发生在一个web服务器,但是观察者的作用发生在Sidekiq工人,和after_save不能保证数据和可用的工人准备的时候。

The config for RSpec looks like this in spec/spec_helper.rb

RSpec的配置类似于spec/spec_helper.rb

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

I have also adjusted rake db:create so that it draws from a structure.sql file. In lib/tasks/db.rb

我还调整了rake db:create,使它从一个结构中提取。sql文件。在lib /任务/ db.rb

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

I did this so that I could run tests to ensure that the database was enforcing foreign key constraints.

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

Is there a way to run both the after_save and after_commit callbacks without making the Rspec use_transactional_fixtures == false?

是否有一种方法可以同时运行after_save和after_commit回调,而不会使Rspec use_transactional_fixtures == false?

Or, is there a way to set config.use_transactional_fixtures to 'false' just for that test, or for that test file?

或者,是否有设置配置的方法。use_transactional_fixtures改为false,只针对那个测试,还是那个测试文件?

1 个解决方案

#1


4  

To properly execute database commits you need to enable config.use_transactional_fixtures, but I'd recommend you consider different strategies as that option is disabled by default for the sake of good test design, to enforce your tests to be as unitary and isolated as possible.

要正确执行数据库提交,需要启用配置。use_transactional_fixtures,但是我建议您考虑使用不同的策略,因为为了更好的测试设计,默认情况下禁用了这个选项,以尽可能地统一和隔离测试。

First, you can run ActiveRecord callbacks with #run_callbacks(type), in your case model.run_callbacks(:commit)

首先,您可以使用#run_callbacks(类型)运行ActiveRecord回调,在您的示例模型。run_callbacks(:commit)中

My preferred strategy is to have a method with the logic you want to run, then declare the hook using the method name, then test the method behavior by calling it directly and test that the method is called when the hook is run.

我的首选策略是使用希望运行的逻辑的方法,然后使用方法名声明钩子,然后通过直接调用它来测试方法行为,并测试在运行钩子时调用该方法。

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

That assumes that whatever logic you have on the callback is not essential for the model state, i.e. doesn't change it to something you need to consume right away, and that any other model that interacts with it is indifferent to it. And as such isn't required to run in a test context. That is a powerful design principle that in the long term prevents callbacks to get out of control and generate dependencies for tests by demanding some property unrelated to the unit you are testing to be setup only to be consumed on a callback that you don't care about at that moment.

它假设回调的逻辑对模型状态来说不是必需的,也就是说,它不会改变为需要立即使用的东西,与它交互的任何其他模型对它都无关紧要。而且,在测试环境中不需要这样做。,长期是一个功能强大的设计原则,防止回调失控并生成依赖项测试要求一些产权无关您测试的单元上设置只能使用一个回调,你不关心。

But, in the end you know your domain and design requirements better than a stranger so, if you really need the after_commit to run, you can force it with model.run_callbacks(:commit). Just encapsulate that on your factory/fixture and you don't have to remember it every time.

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

#1


4  

To properly execute database commits you need to enable config.use_transactional_fixtures, but I'd recommend you consider different strategies as that option is disabled by default for the sake of good test design, to enforce your tests to be as unitary and isolated as possible.

要正确执行数据库提交,需要启用配置。use_transactional_fixtures,但是我建议您考虑使用不同的策略,因为为了更好的测试设计,默认情况下禁用了这个选项,以尽可能地统一和隔离测试。

First, you can run ActiveRecord callbacks with #run_callbacks(type), in your case model.run_callbacks(:commit)

首先,您可以使用#run_callbacks(类型)运行ActiveRecord回调,在您的示例模型。run_callbacks(:commit)中

My preferred strategy is to have a method with the logic you want to run, then declare the hook using the method name, then test the method behavior by calling it directly and test that the method is called when the hook is run.

我的首选策略是使用希望运行的逻辑的方法,然后使用方法名声明钩子,然后通过直接调用它来测试方法行为,并测试在运行钩子时调用该方法。

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

That assumes that whatever logic you have on the callback is not essential for the model state, i.e. doesn't change it to something you need to consume right away, and that any other model that interacts with it is indifferent to it. And as such isn't required to run in a test context. That is a powerful design principle that in the long term prevents callbacks to get out of control and generate dependencies for tests by demanding some property unrelated to the unit you are testing to be setup only to be consumed on a callback that you don't care about at that moment.

它假设回调的逻辑对模型状态来说不是必需的,也就是说,它不会改变为需要立即使用的东西,与它交互的任何其他模型对它都无关紧要。而且,在测试环境中不需要这样做。,长期是一个功能强大的设计原则,防止回调失控并生成依赖项测试要求一些产权无关您测试的单元上设置只能使用一个回调,你不关心。

But, in the end you know your domain and design requirements better than a stranger so, if you really need the after_commit to run, you can force it with model.run_callbacks(:commit). Just encapsulate that on your factory/fixture and you don't have to remember it every time.

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