Rails 3: ActiveRecord observer: after_commit callback doesn't fire during tests, but after_save does fire

好久不见. 提交于 2019-12-07 15:24:28

问题


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.

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.

The config for RSpec looks like this in 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

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?

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


回答1:


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.

First, you can run ActiveRecord callbacks with #run_callbacks(type), in your case model.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.



来源:https://stackoverflow.com/questions/27139012/rails-3-activerecord-observer-after-commit-callback-doesnt-fire-during-tests

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!