Skip to content

Transactional Tests in Ruby on Rails

I’ve recently worked through a rather frustrating issue that turned out to have a simple solution.  I thought I’d share in case others out there were experiencing issues similar to mine.  Your ActiveSupport::TestCase tests (the default in Rails) are configured to run in transactions (unless they are explicitly disabled), so normally one can assume that each test runs on a clean slate of data.  It’s even supposed to work with factories like FactoryGirl (highly recommend using this gem to make your life easier while testing).  As it turns out this isn’t always true: this feature of Rails only works to a point and tends to break with even the simplest cases when running tests in a multithreaded environment.

The Problem

The issue arose when I switched my testing infrastructure over to a combination of Guard and Spork.  As a side note, so far I really like this setup as it runs the tests relevant to the change(s) made whenever your files are modified, and these tests are a bit snappier thanks to Spork.  After making this switch I noticed that my tests were ending in unexpected states (mostly leaving rows behind).  This was causing later, more complex tests to fail when they should not have been failing.

In my case I believe the issue was stemming from the multithreaded environment that is created with Spork (this is how Spork achieves its speed boost), as the tests were working without issue before the change.  It is worth mentioning that the default transactional setup of ActiveSupport::TestCase may also fail with complex tests (depends entirely on the test setup).  This can cause rows being left behind after a test just as my multithreaded issue did.  I’ve never seen this in practice myself but in researching this issue and discussing it online I encountered a few developers who have.  Just something to keep in mind.

The Fix

Thankfully the solution for this is really simple!  There is a fantastic gem out there called database_cleaner that was created to offer developers more flexibility in this situation.  database_cleaner offers a couple of different options for cleaning up changes in your database between tests.  Take a few minutes to read about them, as some are more performant than others but won’t work in all situations.  For my case I stuck with the transactions option (the most performant) but explicitly wrapped each of my tests in one using the DatabaseCleaner methods instead of relying on Rails to do it for me.  Once I installed the gem I simply had to add the necessary DatabaseCleaner calls to my test/test_helper.rb file, and my problems were solved.  My test/test_helper.rb now looks like this:

# test/test_helper.rb
ENV["RAILS_ENV"] = "test"
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'

DatabaseCleaner.strategy = :transaction

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
  #
  # Note: You'll currently still have to declare fixtures explicitly in integration tests
  # -- they do not yet inherit this setting
  fixtures :all

  # Stop ActiveRecord from wrapping tests in transactions
  self.use_transactional_fixtures = false

  setup { DatabaseCleaner.start }
  teardown { DatabaseCleaner.clean }

  # Add more helper methods to be used by all tests here...
end
 

Categories: Programming, Ruby on Rails.

Comment Feed

2 Responses

  1. Hair removal in some areas is essential to replace the areas where its growth has stopped.
    Electrolysis – Electrolysis is a solution which
    utilizes a needle to kill the hair at its root.
    Remember, the foaming action of regular shampoos and detergents
    is for looks only.

  2. People have died on the operating room table even with the best of care.
    However, apart from beauty purposes, nose jobs are also used to
    correct problems that might have occurred due to a misshapen nose,
    such as breathing. Miami won’t need to rely heavily on Lewis, but
    he’ll increase the team’s depth and provide yet another scoring option on arguably the league’s most dangerous offensive team.



Some HTML is OK

or, reply to this post via trackback.