Unit testing ActiveRecord validations and PostgreSQL constraints

September 16, 2010

Some of the more rudimentary aspects of unit testing involve validation. These tests may exercise ActiveRecord or the database constraints created by a migration.

In a chapter from Enterprise Rails, 'Database As a Fortress', author Dan Chak advocates for employing the data integrity features of modern databases, particularly PostgreSQL. With Realized-app, we use PostgreSQL as a persistent data store.

To go beyond just testing the ActiveRecord model validations, in testing a database constraint Dan suggests using the following test_helper.rb method:

def expect_db_error(&block)
  begin
    yield
  rescue ActiveRecord::StatementInvalid
    database_threw_error = true
  rescue
    something_else_threw_error = true
  end
  assert !something_else_threw_error, 'defective case'
  assert database_threw_error && !something_else_threw_error
end

One difficulty with testing database constraints is that the ActiveRecord model validations may catch some errors before they reach the PostgreSQL database. As a work-around, we can add another method:

def svf(obj) obj.save_with_validation(false) end	

Using these test helpers, this test will properly exercise the database constraints defined by a migration.

test 'name too long' do
  expect_db_error { svf(some_model_save(:name => 'name_too_long')) }
end

Such validation tests tend to be similar among different models. Just as concerned_with allowed for division of model logic into discrete files, we place unit test validation tests in a separate /test/unit/validation directory. This prevents cluttering model-specific logic with boilerplate validation checks.