At Pivotal, some of our client projects use plugins from our home-grown social networking platform and rely on Desert to tie them all together. To test this package of plugins we created a project that contains all of our Desert plugins and wrote some rake tasks that run all of their tests. Great, right?
Mostly. We want to ensure that our plugins have the absolute minimum dependencies to function. Let’s pretend we have an UserAuth plugin and a SocialPivots plugin, where UserAuth has no dependencies, but SocialPivots depends on UserAuth. We would like to test these the to plugins in isolation. But, with Desert doing it’s job so well, our UserAuth plugin could have a dependency on the SocialPivots plugins’ models or tables and we would never know it. Everything from SocialPivots is mixed-in and loaded into memory, and all of its migrations have executed, at the time we are running UserAuth’s tests.
What we need is a way to tell Desert to load only the plugin under test, plus its dependencies listed in init.rb
. Hacking Desert and Rails to allow us to specify which plugins to load turned out to be pretty easy. Check it out (full gist here):
Here, we override plugin loading:
# lib/plugin_dependency_limiter.rb
class Rails::Initializer
def load_plugins
# Only load the plugin under test
Rails::Plugin.new("vendor/plugins/#{ENV['PLUGIN']}").load(self)
end
def add_plugin_load_paths
# Do nothing. We'll handle plugin loading ourselves.
end
end
We might have many plugins in vendor/plugins
but we only want to test our Desert plugins. We list them in config/plugins/plugins_to_test.yml
and keep track of them here:
# also in lib/plugin_dependency_limiter.rb
class Rails::Plugin
def self.plugins_of_interest
# Keep track of the Desert plugins we care about
@of_interest ||= YAML.load_file(RAILS_ROOT + "/config/plugins/plugins_to_test.yml").collect
end
def self.tracked_plugins
@tracked_plugins ||= []
end
def require_plugin_with_plugin_tracking(plugin_name)
self.class.tracked_plugins << plugin_name if self.class.plugins_of_interest.include?(plugin_name)
require_plugin_without_plugin_tracking(plugin_name)
end
alias_method_chain :require_plugin, :plugin_tracking
end
Once you can control which plugins are loaded you can expand this to dictate which routes and migrations should be run:
Routes:
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
if ENV['PLUGIN']
Rails::Plugin.tracked_plugins.each do |plugin|
map.routes_from_plugin(plugin)
end
map.routes_from_plugin(ENV['PLUGIN'])
end
end
Migrations:
# db/migration/001_migrate_desert_plugins.rb
class MigrateDesertPlugins < ActiveRecord::Migration
def self.up
Rails::Plugin.tracked_plugins.each do |plugin|
migrate_plugin(plugin, nil)
end
migrate_plugin(ENV['PLUGIN'], nil)
end
def self.down
raise ActiveRecord::IrreversibleMigration
end
end
Let’s run some tests! Though we don’t provide the rake task here, it runs db:drop db:create db:migrate db:test:prepare
before running the plugin tests.
First, the UserAuth plugin that has no dependencies:
$ rake testspec:plugins PLUGIN=user_auth
(in /Users/pivotal/workspace/desert_plugins)
== MigrateDesertPlugins: migrating ===========================================
== CreateUserAuthStuff: migrating ====================================================
-- create_table(:user_auth_stuff)
-> 0.0023s
== CreateUserAuthStuff: migrated (0.0026s) ===========================================
== MigrateDesertPlugins: migrated (0.0076s) ==================================
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -I"lib:test" "/Library/Ruby/Gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb"
....................................
Finished in 0.494227 seconds
36 examples, 0 failures
Now the SocialPivots Plugin, which relies on UserAuth:
$ rake testspec:plugins PLUGIN=social_pivots
(in /Users/pivotal/workspace/desert_plugins)
== MigrateDesertPlugins: migrating ===========================================
# Look, the migrations from UserAuth!
# Look, the migrations from UserAuth!
# Look, the migrations from UserAuth!
== CreateUserAuthStuff: migrating ====================================================
-- create_table(:user_auth_stuff)
-> 0.0201s
== CreateUserAuthStuff: migrated (0.0204s) ===========================================
== CreateSocialPivotStuff: migrating ==============================================
-- create_table(:social_pivot_stuff)
-> 0.0034s
== CreateSocialPivotStuff: migrated (0.0036s) =====================================
== MigrateDesertPlugins: migrated (0.0332s) ==================================
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -I"lib:test" "/Library/Ruby/Gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb"
........................................................................
Finished in 1.381141 seconds
72 examples, 0 failures
I hope this code makes testing Desert plugins easier. Thanks to Pivots Adam and Edward for pairing with me through these solutions.
About the Author