15

Transactional fixtures in rspec prevent after_commit from being called, but even when I disable them with

RSpec.configure do |config|
  config.use_transactional_fixtures = false
end

The after_commit callback does not run.

Here is a rails app with the latest rspec / rails that I have produced the issue on: git://github.com/sheabarton/after_commit_demo.git

shyam
  • 9,134
  • 4
  • 29
  • 44
user545139
  • 935
  • 11
  • 27
  • After_commit callback was added to tests in Rails 5.0+ https://github.com/rails/rails/pull/18458 – justi Nov 12 '19 at 22:19

5 Answers5

28

One way around this is to trigger the commit callbacks manually. Example:

describe SomeModel do
  subject { ... }

  context 'after_commit' do
    after { subject.run_callbacks(:commit) }

    it 'does something' do
      subject.should_receive(:some_message)
    end
  end
end

A little late, but hope this helps others.

JPowell
  • 418
  • 4
  • 6
9

In my case I resolved such problem with database_cleaner's settings placed below:

config.use_transactional_fixtures = false

config.before(:suite) do
  DatabaseCleaner.strategy = :deletion
  DatabaseCleaner.clean_with(:truncation)
end

config.before(:each) do
  DatabaseCleaner.start
end

config.after(:each) do
  DatabaseCleaner.clean
end

Thanks to Testing after_commit/after_transaction with Rspec

Stepan Zakharov
  • 420
  • 6
  • 11
  • This is actually the proper answer in most cases, although the deletion and truncation strategies are much slower than transactions. – averell Jul 28 '14 at 15:23
8

This is similar to @jamesdevar's answer above, but I couldn't add a code block, so I have to make a separate entry.

You don't have the change the strategy for the whole spec suite. You can keep using :transaction globally then just use :deletion or :truncation (they both work) as needed. Just add a flag to the relevant spec.

config.use_transactional_fixtures = false

config.before(:suite) do
  # The :transaction strategy prevents :after_commit hooks from running
  DatabaseCleaner.strategy = :transaction
  DatabaseCleaner.clean_with(:truncation)
end

config.before(:each, :with_after_commit => true) do
  DatabaseCleaner.strategy = :truncation
end

then, in your specs:

describe "some test requiring after_commit hooks", :with_after_commit => true do
bricolage
  • 196
  • 1
  • 4
  • This is a great solution that doesn't involve with object's `run_callbacks` and you don't have to install a new gem! – Hoang Huynh Feb 19 '16 at 11:49
5

If you're using database_cleaner you'll still run into this. I'm using the test_after_commit gem, and that seems to do the trick for me.

lobati
  • 9,284
  • 5
  • 40
  • 61
2

This Gist helped me.

It monkey-patches ActiveRecord to fire after_commit callbacks even if using transactional fixtures.

module ActiveRecord
  module ConnectionAdapters
    module DatabaseStatements
      #
      # Run the normal transaction method; when it's done, check to see if there
      # is exactly one open transaction. If so, that's the transactional
      # fixtures transaction; from the model's standpoint, the completed
      # transaction is the real deal. Send commit callbacks to models.
      #
      # If the transaction block raises a Rollback, we need to know, so we don't
      # call the commit hooks. Other exceptions don't need to be explicitly
      # accounted for since they will raise uncaught through this method and
      # prevent the code after the hook from running.
      #
      def transaction_with_transactional_fixtures(options = {}, &block)
        rolled_back = false

        transaction_without_transactional_fixtures do
          begin
            yield
          rescue ActiveRecord::Rollback => e
            rolled_back = true
            raise e
          end
        end

        if !rolled_back && open_transactions == 1
          commit_transaction_records(false)
        end
      end
      alias_method_chain :transaction, :transactional_fixtures

      #
      # The @_current_transaction_records is an stack of arrays, each one
      # containing the records associated with the corresponding transaction
      # in the transaction stack. This is used by the
      # `rollback_transaction_records` method (to only send a rollback hook to
      # models attached to the transaction being rolled back) but is usually
      # ignored by the `commit_transaction_records` method. Here we
      # monkey-patch it to temporarily replace the array with only the records
      # for the top-of-stack transaction, so the real
      # `commit_transaction_records` method only sends callbacks to those.
      #
      def commit_transaction_records_with_transactional_fixtures(commit = true)
        unless commit
          real_current_transaction_records = @_current_transaction_records
          @_current_transaction_records = @_current_transaction_records.pop
        end

        begin
          commit_transaction_records_without_transactional_fixtures
        rescue # works better with that :)
        ensure
          unless commit
            @_current_transaction_records = real_current_transaction_records
         end
        end
      end
      alias_method_chain :commit_transaction_records, :transactional_fixtures
    end
  end
end

Put this a new file in your Rails.root/spec/support directory, e.g. spec/support/after_commit_with_transactional_fixtures.rb.

Rails 3 will automatically load it in the test environment.

messanjah
  • 8,977
  • 4
  • 27
  • 40
  • This worked for me but made my specs unusably slow. Going to swap to using after_save instead for now though I'm concerned it may not 100% align with my business logic in all cases. – steven_noble Jun 20 '13 at 23:46