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?
source to share
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.
source to share