Testing after_commit with RSpec and mocking

20,330

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
Share:
20,330
Mihail Davydenkov
Author by

Mihail Davydenkov

Updated on January 25, 2020

Comments

  • Mihail Davydenkov
    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?