blog.kotamiyake.me

為せば成る、為さねば成らぬ何事も

技術ネタもこちらで書くことにしました!


みなさんはテストを書いていますか?今回は先日RSpecでテストを書いている時に起こったafter_commitが動かないという問題についての内容となっています。

実行環境

  • rails 4.2.10
  • rspec-rails 3.5.2

RSpecのafter_commitのテストで問題発生

先日、RSpecでafter_commitのテストしようと以下のようなコードを書いたのですが思った通りの結果になりませんでした。

class Model < ActiveRecord::Base
  after_commit :do_somthing

  private

  def do_something
    update_columns(total: 100)
  end
end
it 'do something after save' do
  model.save
  expect(model.total).to eq(100)
end

どうにも原因がわからずGoogleで調べてみると、use_transactional_fixturesがtrueとなっているとcommitが行われず、一つ一つのexampleがtransaction内で実行され、テスト後にはロールバックされるようになるということです。

ちなみにRSpecのuse_transactional_fixturesはそのままRails側の設定に渡されるようになっています。

          if ::Rails::VERSION::STRING > '5'
            self.use_transactional_tests = RSpec.configuration.use_transactional_fixtures
          else
            self.use_transactional_fixtures = RSpec.configuration.use_transactional_fixtures
          end
          self.use_instantiated_fixtures  = RSpec.configuration.use_instantiated_fixtures

https://github.com/rspec/rspec-rails/blob/v3.5.2/lib/rspec/rails/fixture_support.rb#L25-L30

## 強制的にcommitを実行して問題解決

ではどうやってcommitさせるかというと保存処理をしたあとに強制的にtransaction情報をクリアして、そのあとにrun_callbacksというメソッドを使ってcommitを実行させます。

it 'do something after save' do
  model.save
  model.send(:force_clear_transaction_record_state)
  model.run_callbacks(:commit)
  expect(model.total).to eq(100)
end

これでafter_commitが正常に動作し無事テストが通るようになりました。

https://stackoverflow.com/questions/33940268/after-commit-callback-on-update-doesnt-trigger を参考に最初はmodel.send(:clear_transaction_record_state)を試してみたのですがうまくいきませんでした。

Railsのコードを読んでみるとどうやらclear_transaction_record_stateだとtransaction情報がうまくクリアされておらず、正しくはforce_clear_transaction_record_stateを呼び出す必要がありました。

    # Clear the new record state and id of a record.
    def clear_transaction_record_state #:nodoc:
      @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
      force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
    end


    # Force to clear the transaction record state.
    def force_clear_transaction_record_state #:nodoc:
      @_start_transaction_state.clear
    end

https://github.com/rails/rails/blob/v4.2.10/activerecord/lib/active_record/transactions.rb#L379-L388

まとめ

それぞれの設定の意味を理解することはとても大事です。恥ずかしながら今回はそれが出来ていなかったために遭遇した問題でした。

みなさんもデフォルトのままで特に気にしていなかった設定を見返してみるのもいいかもしれません。