Rails4での話。(3以下は未確認。おそらくRails3でも同じ)
tl;dr
- rails4ではテスト実行時に
transactional_fixturesのせいでafter_commitが呼ばれない - test_after_commitを使えば解決できる
- rails5では修正されている
問題
test実行時にActiveRecord::Callbacksのafter_commitが呼ばれない
そのため、意図通りに動かない・テストが通らないということになる
after_commitのドキュメントには以下のように書いてある
https://github.com/rails/rails/blob/v4.2.5/activerecord/lib/active_record/transactions.rb#L235-L237
Note that transactional fixtures do not play well with this feature. Please
use the +test_after_commit+ gem to have these hooks fired in tests.
意訳すると、
テスト実行時に
transactional fixturesを使ってるとafter_commitが呼ばれないのでtest_after_commitを使ってね。
transactional_fixtures
http://anti-pattern.com/transactional-fixtures-in-rails から
Transactional fixtures simply wrap each test in a database transaction and start a rollback at the end of it, reverting the database back to the state it was in before the test.
If the database was empty before the test, it will roll it back and be empty after the test. If the database had preloaded test data (like fixtures) in it before the test, it will roll it back and have that same data after the test.
Even though this feature is unrelated to fixtures themselves, fixtures are the default way to create test data in Rails, hence the name “transactional fixtures”.
意訳すると、
Transactional fixturesはテストごとにDBのtransactionを開始しテスト終了後にDBをrollbackされ、DBはテスト実行前の状態に戻る。
テスト実行時にDBが空っぽならば空っぽに戻るし、fixturesなどでデータが用意されていれば同じデータを持った状態に戻る。
この機能はfixturesとは関係ないが、fixturesがrailsのテストデータを用意するデフォルトの方法なので、transactional fixturesという名前が付いている
つまり、database_cleaner等と同等の機能。
(rails4ではdataase_cleaner等は基本的には必要ない。ただ、mongodbなどには対応していないので、そういう時にはdatabase_cleaner等を使う必要がある)
- テスト実行ごとにtransactionを開始してrollbackをしている
- つまりDBにcommitをしていない
- たぶん!(実装ちゃんと見てないのでわからないが、DB的にはcommitしていないだけでなのかなと思う)
- そのため
after_commitが呼ばれない- (そのためかどうか、は実装ちゃんと見てないのでわからない)
という感じなのだと思う。
解決策
- railsのドキュメントにある通り、 test_after_commitを使う
-
transactional_fixturesをoffにしdatabase_cleanerを使い、DatabaseCleaner.strategyを:transactionではなく:truncationにする- 本当に
after_commitが呼ばれるかは未確認。動く気がしているだけ
- 本当に
Rails5
rails5ではtransactional_fixturesを使っていてもafter_commitが呼ばれるようになっている
あと、transactional_fixturesではなくてtransactional_testsという名前になっている
関連PR
https://github.com/rails/rails/pull/18458 Support after_commit callbacks in transactional fixtures
https://github.com/rails/rails/pull/19273 ‘test_after_commit’ gem is not require in Rails 5, remove note from doc ...
https://github.com/rails/rails/pull/19282 Renaming transactional fixtures to transactional tests
これまでに書いた大半のことが書いてある素晴らしいやつ