问题
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