company-logo

Tips for Testing Plugins

I’ve been doing some heavy-duty plugin work recently, and bumped up against the same problem again — how do you test plugins? Specifically, how do you test a plugin that extends Rails core classes and therefore requires a Rails environment to really be tested? How do you solve the chicken and egg problem of loading Rails pieces inside a plugin that also needs to have a Rails application outside of it?

The answer, not surprisingly, is “it depends”, mostly on how complicated your plugin is and what parts of Rails you need to load. In this post, I’ll be going through some tips for making specific pieces of an ordinary Rails test environment available to your plugin tests. The tips here are a combination of my own experience with ideas that some popular plugins use for testing.

Note 1: If you’ve bought my book, Professional Ruby on Rails, you’ll know that Chapter 15 on plugins covers testing. Think of this as revisiting the same material, about a year or so after I originally wrote it. In fact, print this post out and insert it into Chapter 15.

Note 2: If you haven’t bought my book, Professional Ruby on Rails, might I humbly suggest it? I like it very much.

Note 3: Many of the issues covered here can be managed with mock objects, if your plugin is simple enough and/or you have the patience to set up the mocks.

The basic problem comes up if your plugin extends Rails core — especially ActiveRecord, where database issues come into play.

The goal here is to have the tests for your plugin be completely stand-alone, requiring no external Rails project in order to run, and especially not requiring any specific feature or naming convention in an external project

Use a Reference Application

I recommend writing your plugin inside a scratch application that you can use to exercise the plugin from more of an acceptance test standpoint. You wouldn’t need to distribute this applicaiton, but you can write acceptance tests in the reference application if you want. The main point is just to have a clean space for plugin development — I find that if I try to actively develop a plugin inside an application that I’m also actively developing that can lead to problems separating plugin functionality from app functionality.

Load The Rails Environment

Loading the Rails environment cleanly such that all the core pieces are in place is actually something of a black art. I’m sure there are subtleties here that I don’t completely understand, but there seem to be about three basic approaches.

You can load from the parent application, generally by doing one of the two following bits:

RAILS_ENV = 'test'
require File.expand_path(File.join(File.dirname(__FILE__),
    '../../../../config/environment.rb'))

or

 require File.expand_path(File.join(File.dirname(__FILE__),
    '../../../../test/test_helper'))

The second version just loads test helper which runs the first two lines anyway.

This is admirably straightforward, especially compared to what we’re going to see in a minute, but does require the plugin to be tested within the context of an external application.

If that bothers you, you can try to load the gem versions of Rails core directly. In your plugins test directory, create your own test_helper file, and do something like this (this code is from acts_as_taggable_on_steroids:

begin
  require File.dirname(__FILE__) + '/../../../../config/environment'
rescue LoadError
  require 'rubygems'
  gem 'activerecord'
  gem 'actionpack'
  require 'active_record'
  require 'action_controller'
end

The advantage of this is that you are no longer dependent on having an actual external Rails app. The downside is that you are now dependent on whatever gem version of Rails is installed on your machine. I also have had some weird problems getting this to work for me, with some classes not loading. It’s almost certainly my error, though.

Or, you can do what Shoulda does and install a mini Rails app inside your plugin. It’s so crazy it just! might! work!

The test directory for Shoulda has a subdirectory called rails_root. Inside is a stripped-down Rails app. The app directory contains classes used in the tests. The config directory contains the actual boot.rb file from a Rails app, a database.yml that we’ll discuss in a moment. The environment.rb looks like this:

old_verbose, $VERBOSE = $VERBOSE, nil
RAILS_GEM_VERSION = '2.1.0' unless defined? RAILS_GEM_VERSION
$VERBOSE = old_verbose

require File.join(File.dirname(__FILE__), 'boot')

Rails::Initializer.run do |config|
  config.log_level = :debug
  config.cache_classes = false
  config.whiny_nils = true
end

There’s an empty file corresponding to the actual environment. The db directory I’ll also get to in a second, and the rest of it is basically just a skeleton. To load this, you need a test_helper.rb file like this:

ENV['RAILS_ENV'] = 'test'
rails_root = File.dirname(__FILE__) + '/rails_root'
require "#{rails_root}/config/environment.rb"

In other words, the test directory for the plugin looks like this:

test/
   |-------fixtures
   |-------functional
   |-------rails_root
                   |-----------app
                                    |---------controllers
                                    ...etc
                   |-----------config
                   |-----------db
                                    |---------migrate
                   |-----------log
                   ...etc (but no test directory)
  |--------unit

The advantage here is that it’s about as close to a real Rails environment as you can get, plus you can seed the app folder with classes to test on. The downside is that it’s kind of a pain to set up, relative to the other versions.

By the way, no matter what version you use, the test_helper.rb file you create needs to be required into all your test files.

Active Record Database Connections

In the book, I go through a convoluted thing where you monkey patch ActiveRecord to not need a database. It’s opaque and awkward.

Luckily there’s a better way. You’ll need a database.yml file. If you’re using the full app-style setup, it goes in the /test/rails_root/db directory of your plugin. Otherwise, just put it in the /test directory. It should look like this:

test:
  :adapter: sqlite3
  :dbfile: ":memory:"

This sets up an sqlite3 database in memory for your tests. Obviously, there’s a limit to how much data you can use for this, but I’d be pretty surprised if your plugin tests were driving that much data.

If you are using the full-app style, like Shoulda does, the file will be loaded (the YAML file and the RAILS_ENV you set need to match). Otherwise, you need to load the file in your test_helper.rb file:

ActiveRecord::Base.configurations =
     YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
ActiveRecord::Base.establish_connection('test')

That loads the yaml file and then ties ActiveRecord to the correct environment.

Loading Database Schema

If your plugin requires a specific schema, as, say acts_as_taggable does, you need to load that schema. There are a couple of options.

You can store the schema in a Rails schema.rb file:

ActiveRecord::Schema.define :version => 0 do
# include table descriptions using migration syntax
end

Then you can load the schema in the test_helper file with:

load(File.dirname(__FILE__) + '/schema.rb')

That’s a potential DRY violation, depending on how your plugin is creating it’s database schema for regular (non-test) use. Another option, if you have migrations, is to load the migrations with something like this:

ActiveRecord::Migration.verbose = false
ActiveRecord::Migrator.migrate("#{RAILS_ROOT}/db/migrate")

That example is from Shoulda, if you aren’t using the full-app style, replace the directory with your own location for the plugin migrations.

Loading Fixtures

This applies no matter which way you are loading the rest of the environment, you need to explicitly set the directory with test fixtures — this example assumes that the fixture directory is directly under the test directory:

Test::Unit::TestCase.fixture_path = File.join(File.dirname(__FILE__), "fixtures")

You’ll probably want to also include the following in your test helper, as in the “normal” rails test_helper file:

class Test::Unit::TestCase #:nodoc:
  self.use_transactional_fixtures = true
  self.use_instantiated_fixtures  = false
end

Though now that I’m looking closer, I see that Shoulds sets the first line to false.

Sample Classes

Often, you want to create sample classes for use in your plugin:

class Banana < ActiveRecord:Base
  acts_as_muffinable
end

In the full-app-style setup, this can go in /test/rails_root/app/models. If not, I often put these in the same file as my actual test units, which guarantees they get loaded and makes the class very visible from the test code.

Other Requirements

Other needed gems, and (I think) plugins can be loaded by using require statements in the test_helper file.

  1. Aaron Pfeifer Reply

    Great writeup… definitely covers the basics for plugin testing. However, I would *highly* recommend taking a look at the plugin_test_helper library (http://github.com/pluginaweek/plugin_test_helper). This encapsulates the entire environment setup process and has made it really easy to test plugins in isolation. Most of the plugins in the pluginaweek project use this library for setting up test environments.

  2. Bill Reply

    AHHG! This is the 10th tutorial I’ve Googled that has told me how to SETUP the tests for my Rails plugin, and the 10th that has not told me how to run the damn things (I know, rake test:plugins to run tests for all plugins, but what about for one?)!

    Sorry. Not your fault any more than the other 10. But this seems like a pretty obvious need that Google is woefully underprepared for.

Leave a Reply

*

captcha *