Configuring database_cleaner with Rails, RSpec, Capybara, and Selenium

If you write Rails code, or any Ruby code that interacts with a database, and you also write automated tests, chances are you have heard of or used the database_cleaner gem. It’s a terrific gem that abstracts away the various ORM APIs for getting the DB into a “blank slate” state.

Periodically I start a new project using Rails, with RSpec, Capybara, and Selenium for acceptance testing, and a short way into it I find myself banging my head against bizarre inconsistencies with the test database. I’ll set up some records in the test DB, only to have the Selenium-driven browser-based tests act like those records never existed. Eventually, I’ll realize what I did wrong and curse my feeble brain for not remembering the last time I solved the same problem.

The problem is always the same: the tests are being wrapped in database transactions, so any code running outside the actual test process (like, say, a server process servicing a Selenium-driven browser request) does not see the database fixture I’ve so carefully assembled.

I just walked a pairing client through the same fix, so in the interests of remembering the steps, and hopefully preventing some other folks from tearing their hair out, here are the steps needed.

First of all, and this is very important, go into spec/spec_helper.rb and change this line:

config.use_transactional_fixtures = true

To:

config.use_transactional_fixtures = false

This will disable rspec-rails’ implicit wrapping of tests in a database transaction. Without disabling this, none of the following configuration will matter.

Now configure database_cleaner. I usually create a separate file called spec/support/database_cleaner.rb for this. Inside, I put something like this:

RSpec.configure do |config|

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

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

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

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

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

end

Let’s take that step by step.

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

This says that before the entire test suite runs, clear the test database out completely. This gets rid of any garbage left over from interrupted or poorly-written tests—a common source of surprising test behavior.

config.before(:each) do
  DatabaseCleaner.strategy = :transaction
end

This part sets the default database cleaning strategy to be transactions. Transactions are very fast, and for all the tests where they do work—that is, any test where the entire test runs in the RSpec process—they are preferable.

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

This line only runs before examples which have been flagged :js => true. By default, these are the only tests for which Capybara fires up a test server process and drives an actual browser window via the Selenium backend. For these types of tests, transactions won’t work, so this code overrides the setting and chooses the “truncation” strategy instead.

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

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

These lines hook up database_cleaner around the beginning and end of each test, telling it to execute whatever cleanup strategy we selected beforehand.

And that’s it!

Note that this is all for RSpec, and does not cover Cucumber configuration.

Hopefully this will help someone else out there avoid the frustrations I’ve run into!

EDIT: A few people have asked me why I don’t just force all threads to share the same ActiveRecord connection, as demonstrated in this Gist. A few reasons:

  • Using database_cleaner implies that I want ORM neutrality. database_cleaner supports ActiveRecord, DataMapper, MongoMapper, and others. The solution linked above only works for ActiveRecord.
  • It’s a monkey-patched kludge which will only work so long as AR refrains from changing its connection-sharing internals. And frankly I’m not sure I trust it to work across all Ruby VM and database combinations (UPDATE: And indeed, I’ve now seen two different people say there are race conditions with the current Postgres adapter). I’d be more inclined to use it if ActiveRecord had a published configuration option which was known to work in all contexts.
  • As I stressed above, I’m careful to set things up so that only the tests that need them fall back to truncation. Since :js => true tests generally don’t form the bulk of my suite (and since they are unavoidably slow anyway, due to the overhead of driving a browser), I’m not overly concerned about the added overhead. Perhaps if all of my acceptance tests drove a live browser I’d be more worried about it.
  • In cases where database truncation is taking up a significant amount of test time, you can usually speed things up with some judicious control of which subset of tables get truncated for a given test. This is something database_cleaner makes pretty easy. Maybe that would make a good topic for a followup post.
  • UPDATE: Oh yeah, and as @donaldball points out, sharing a transaction between test and test server means acceptance tests don’t run quite the same as they would in production. Specifically, they’ll never trigger after_commit hooks.