Testing after_commit with RSpec and mocking
Solution 1
Try to use test_after_commit gem
or add following code in spec/support/helpers/test_after_commit.rb - Gist
Solution 2
I thought Mihail Davydenkov's comment deserved to be an answer:
You can also use
subject.run_callbacks(:commit)
.
Also note that this issue (commit callbacks not getting called in transactional tests) should be fixed in rails 5.0+ so you may wish to make a note to remove any workarounds you may use in the meantime when you upgrade. See: https://github.com/rails/rails/pull/18458
Solution 3
I'm using DatabaseCleaner, with a configuration where I can easily switch between transaction and truncation, where the former is preferred, because of speed, but where the latter can be used for testing callbacks.
RSpec before
and after
handlers work with scopes, so if you want to make truncation a scope, define a before
handler;
config.before(:each, truncate: true) do
DatabaseCleaner.strategy = :truncation
end
And now to use this configuration for a describe
, context
or it
block, you should declare it like:
describe "callbacks", truncate: true do
# all specs within this block will be using the truncation strategy
describe "#save" do
it "should trigger my callback" do
expect(lead).to receive(:send_to_SPL)
lead = Lead.create(init_hash)
end
end
end
Complete hook configuration: (store in spec/support/database_cleaner.rb
)
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.strategy = :transaction
end
config.before(:each, truncate: true) do
DatabaseCleaner.strategy = :truncation
end
config.before(:each) do
DatabaseCleaner.start
end
config.append_after(:each) do
DatabaseCleaner.clean
end
end
Solution 4
Update for Rails5.
Callback handling has indeed been fixed, but you may still need to use #reload
liberally.
An example:
Given a model that defines an after-create callback like so:
after_create_commit { assign_some_association }
You can spec this behavior with:
describe "callbacks" do
describe "assigning_some_association" do
subject(:saving) { record.save!; record.reload } # reload here is important
let(:record) { build(:record) }
it "assigns some association after commit" do
expect{ saving }.to(
change{ record.some_association_id }.from(nil).to(anything)
)
end
end
end
Mihail Davydenkov
Updated on January 25, 2020Comments
-
Mihail Davydenkov over 4 years
I have a model Lead and a callback:
after_commit :create, :send_to_SPL
I am using Rails-4.1.0, ruby-2.1.1, RSpec.
1) This spec is not passing:
context 'callbacks' do it 'shall call \'send_to_SPL\' after create' do expect(lead).to receive(:send_to_SPL) lead = Lead.create(init_hash) p lead.new_record? # => false end end
2) This spec is not passing too:
context 'callbacks' do it 'shall call \'send_to_SPL\' after create' do expect(ActiveSupport::Callbacks::Callback).to receive(:build) lead = Lead.create(init_hash) end end
3) This one is passing, but I think it is not testing after_commit callback:
context 'callbacks' do it 'shall call \'send_to_SPL\' after create' do expect(lead).to receive(:send_to_SPL) lead.send(:send_to_SPL) end end
What is the best way to test after_commit callbacks in Rails?