Rails 3: ActiveRecord observer: after_commit callback does not fire during tests, but after_save does fire

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

.

eg.

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 as a penalty during tests. I need to be able to use after_commit

for this particular model, because in some situations the change to "thing" happens on the web server, but the observer effect happens on the Sidekiq worker, and after_save

does not work to ensure that the data has been committed and available when the worker is ready to this.

The configuration for RSpec looks like this: spec / spec_helper.rb

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

      

I also adjusted rake db:create

so that it is fetched from the 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 to run tests to ensure that the database enforces foreign key constraints.

Is there a way to trigger after_save and after_commit callbacks without using Rspec use_transactional_fixtures == false?

Or, is there a way to set config.use_transactional_fixtures to "false" for this test only or for this test file?

+3


source to share


1 answer


For proper execution of database transactions you need to enable config.use_transactional_fixtures

, but I would recommend considering different strategies as this option is disabled by default for the sake of good test design, so that your tests are as single and isolated as possible.

First, you can trigger ActiveRecord callbacks with #run_callbacks(type)

, in your casemodel.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 and then test the behavior of the method by calling it directly and check if 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

      

This assumes that whatever logic you have on the callback is not essential to the state of the model, that is, it does not change it to something you need to consume immediately, and that any other model that interacts with it is indifferent to it. ... And as such is not required to run in a test context. This is a powerful design principle that, in the long run, prevents callbacks to get out of hand and generate dependencies for tests, requiring some property unrelated to the device you are testing to be configured only for use on the callback that you need don't like it around this moment.

But in the end, you know your domain and design requirements better than a stranger, so if you really need after_commit

to get started, you can force it with model.run_callbacks(:commit)

. Just encapsulate this in your factory / fixture and you don't have to remember it every time.

+4


source







All Articles