custom transaction doesn't work with database_cleaner in rspec
Asked Answered
S

1

7

In our Rails 4.0 application using MySql we use rspec together with the database_cleaner gem configured with strategy :transaction to cleanup our database for every test case. If we have custom transactions, which should be rollbacked, it doesn't work.

Without database_cleaner gem and just using the standard way:

config.use_transactional_fixtures = true

everything works as aspected. But for running feature tests with JavaScript we need database_cleaner to change the fixture deletion strategy to :truncation.

How can we use database_cleaner together with custom transactions and why does it differ to the standard rspec transaction strategy?

Scissors answered 9/8, 2013 at 9:18 Comment(1)
I'm sure you know about: DatabaseCleaner.strategy = :deletion / DatabaseCleaner.strategy = :truncation, as you are mentioning it yourself. I am still interested though if this is a problem within database_cleaner's :transaction strategy, or if this is just a general issue with nested transactions sometimes behaving unexpectedly.Kristie
H
11

The problem is that database_cleaner calls ActiveRecord.rollback at the end of a test -- which you also use in your code. InnoDB/MySQL does not support true nested transactions so nested transactions in code are not treated truly as transactions unless they are explicitly called to do so.

Consider this block (from the ActiveRecord docs):

User.transaction do
  User.create(username: 'Kotori')
  User.transaction do
    User.create(username: 'Nemu')
    raise ActiveRecord::Rollback
  end
end

What do you expect to happen after the Rollback call? You expect it to roll back the Nemu user, and leave you with Kotori, right? Well, what actually happens is that Kotori and Nemu are both created. The Rollback doesn't trigger because you're in a nested transaction (and AR only cares about the parent, currently) and the parent transaction (which has an actual db transaction) does not see the Rollback call -- as it is called in an isolated block. It's weird.

Solution:

Klass.transaction(requires_new: true)

If you set requires_new, ActiveRecord will use or pseudo-use a nested transaction (for Postgres it'll make nested transactions, for MySQL/InnoDB it will make savepoints). When you call a rollback with ActiveRecord, it'll figure out its scope and issue the proper rollback.

The other solution is to use truncation or deletion as a strategy for your tests that involve transactions.

Heffernan answered 23/10, 2013 at 18:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.