Testing with foreign key constraints

I’m not yet so enlightened that all of my Rails unit and functional tests run without accessing the database. Indeed, I’m still using YAML fixtures to populate the database for testing.

I also insist on having foreign key constraints in the database, a thing that’s not exactly encouraged by Rails, but which is quite possible nonetheless. The various plugins from RedHill Consulting are a big help.

But then, when you feel all warm and cosy due to the additional safety at the database-level, you’re suddenly trapped by a snag: Sooner or later you find that your fixtures contain dependencies among objects that preclude any attempt at clever ordering by violating one foreign key constraint or another. Fixture files are loaded one after another in their entirety and when an object in an earlier fixture refers to an object in a later fixture, the database aptly notices as an inconsistency.

Well, you may think, it is an inconsistency, but only a temporal one. After all the fixture files are loaded, everything is consistent again. That’s the clue. We need to tell the database that, yes, indeed, things may be inconsistent for a time, but we’ll be cleaning up, promise. The good thing is that there is even an SQL standard-compliant way to express this promise.

  START TRANSACTION
  SET CONSTRAINTS ALL DEFERRED
  COMMIT

If you use transactional fixtures, the transaction bracket is already provided by Rails, but there’s no pretty way to sneak in the "SET CONSTRAINTS ..." line. There are two ways of slightly different brutality. First, you can edit activerecord/lib/fixtures.rb and just insert the required line.

  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
    ...
    connection.transaction(Thread.current['open_transactions'].to_i == 0) do
      # insert the following line
      connection.execute("SET CONSTRAINTS ALL DEFERRED")
      ...
    end
    ...
  end

Alternatively, you can overwrite the entire method in, say, <railsapp>/lib/transactional_fixture_loading_hack.rb like this

require 'active_record/fixtures'

Fixtures.class_eval do
  def self.create_fixtures(fixtures_directory, table_names, class_names = {})
    ...
    connection.transaction(Thread.current['open_transactions'].to_i == 0) do
      # inserted line
      connection.execute("SET CONSTRAINTS ALL DEFERRED")
      ...
    end
    ...
  end

Whatever you do, you’ll have to inspect your code whenever you update your Rails version.

We’re still not done, unfortunately. The database defers only those constraints that are deferrable. Have a look at the Foreign Key Migrations Plugin for how to achieve this.

As a matter of convenience, I suggest that in your test_helper.rb you add a method that loads all your fixtures

class Test::Unit::TestCase
  self.use_transactional_fixtures = true

  def self.load_all_fixtures
    fixtures :users, :thingamajigs, :gadgets, :widgets
  end
end

Then, in a testcase class you can use it like this

require File.dirname(__FILE__) + '/../test_helper'

class ThingamajigTest < Test::Unit::TestCase
  load_all_fixtures

  ...
end

Note that with transactional fixtures this results in each fixture file loaded only once for all the tests.

So, there we are a last. Or those with a reasonable DBMS, I might say. For, of course, this technique is no use, if your database does not support deferrable constraints. PostgreSQL for one does support them.

XML round-trip testing for Rails resources

I’ve recently started to add XML support to a Rails application, meaning that the application provides data in XML format, if the request asks for it, and it understands XML data on create or update.

To keep the application as well as myself sane, I’ve written a test that ensures the round trip of getting XML and updating an object by sending XML works. This is only a very basic test and there surely is more that can and should be tested.

class XmlRoundtripTest < ActionController::IntegrationTest

  RESOURCES = [
    :people, :things
  ]
  fixtures :stuff, *RESOURCES

  def self.assert_roundtrippability_for(*resources)
    resources.each do |resource|
      define_method("test_xml_roundtrip_for_#{resource}") do
        @user = user
        @user.logs_in

        xml = @user.gets_xml(resource)
        @user.changes_object_name!(xml)

        old_version = @user.extracts_lock_version(xml)
        @user.sends_xml(xml, resource)

        # Make sure that the resource was really updated
        new_version = @user.extracts_lock_version(@user.gets_xml(resource))
        assert_equal(old_version + 1, new_version)
      end
    end
  end

  assert_roundtrippability_for *RESOURCES

  def user
    open_session do |user|
      def user.gets_xml(resources, id = 1)
        get "/#{resources}/#{id}.xml"
        assert_response :success
        @response.body
      end

      def user.sends_xml(xml, resources, id = 1)
        put "/#{resources}/#{id}.xml", xml, :content_type => 'application/xml'
        assert_response :success
        @response.body
      end

      def user.changes_object_name!(xml)
        # arbitrarily change an attribute we know is there
        xml.gsub!(%r{<name>(.*?)</name>}, '<name>X\1Y</name>')
      end

      def user.extracts_lock_version(xml)
        xml =~ %r{<lock-version>(\d+)</lock-version>}
        $1.to_i
      end
    end
  end
end

Rails valid markup testing

Coda Hale has given the intersection of the Rails and web standards communities a very nice plugin for keeping your markup clean. It is aptly, if slightly pretentiously, named Responsible Markup.

But let’s be honest, do you really want to spend much time on writing tests for your markup? After all, it’s taking long enough to get the markup right. Ruby to the rescue! Why write tests when we can get Ruby to write them?

Here’s what I’m using.

class ValidMarkupTest < Test::Unit::TestCase
  ResponsibleMarkup::validator_uri = 'http://localhost/w3c-markup-validator/check'

  def self.validate_markup_for_resources(*resources)
    default_actions = {
      :index => { :method => :get, :template => 'list' }, 
      :new   => { :method => :get, :template => 'new' },
      :edit  => { :method => :get, :id => 1, :template => 'edit' }
    }
    if resources.last.kind_of?(Hash)
      actions = resources.pop
      actions.each do |action, options|
        options.reverse_merge!(default_actions[action] || {})
      end
    else
      actions = default_actions
    end

    resources.each do |resource|
      fixtures resource

      controller_class = "#{resource.to_s.camelize}Controller".constantize

      actions.each do |action, options|
        options = options.dup

        define_method("test_action_#{action}_for_#{resource}_returns_valid_markup") do
          @controller = controller_class.new

          request_method    = options.delete(:method)
          expected_template = options.delete(:template)

          send(request_method, action, options)

          assert_response :success
          assert_template expected_template
          assert_valid_markup
        end
      end
    end
  end

  validate_markup_for_resources :thingamuhjigs, :humdingers, :gizmos

  def setup
    @request  = ActionController::TestRequest.new
    @response = ActionController::TestResponse.new
    login_test_user
  end

  def assert_valid_markup
    assert_doctype(:xhtml_10_strict)
    assert_content_type
#    assert_compatible_empty_elements
    assert_no_empty_attributes
    assert_no_long_style_attributes
    assert_unobtrusive_javascript :allowed => [ :inline_events, :blank_hrefs ]
    assert_valid_html
  end

end

The idea is to have separate the testing task into three parts.

  • A generic, lightly configurable, method for defining tests: validate_markup_for_resources
  • A setup method that is specific to your application. In the example above, I need to login a user.
  • A assert_valid_markup method that contains assertions for your validation goals.

Configuration

By default, validate_markup_for_resources generates tests for index, new, and edit actions. For the edit test, it assumes that an object with the id 1 exists. If you need to change the request options used by default, you can override them like this

validate_markup_for_resources :gizmos, :id => 2, :template => 'gizmo', :\other_param => 'xyz'

Running

Normally, by Rails convention, the above example test would belong in test/functional. However, even when using a validator installed on the local machine, it takes quite some time to run. So, in order not to slow down my functional tests too much, I’ve created a new directory, test/markup and run the tests in there with

$ rake test:markup

This rake task is provided by the following snippet in lib/tasks/markup.rake.

namespace :test do
  desc "Run the checks for valid markup defined in test/markup"
  Rake::TestTask.new(:markup => "environment") do |t|
    t.libs << "test"
    t.pattern = 'test/markup/**/*_test.rb'
    t.verbose = true
  end
end

Incidentally, yes, I know that the markup of this blog is broken. Blame WordPress.