Not sure if this is the correct way to go about testing rake tasks but it works pretty well. Hit up the comments if there is a better way.
So here is an example rake task I want to test:
[lib/tasks/app.rake]
namespace :app do
namespace :options do
desc "refreshes option values"
task :refresh => :environment do
options = YAML.load_file("config/options.yml")
options.each do |attrs|
option = Option.find_or_initailize_by_name(attrs["name"])
option.update_attribute("value", attrs["value"])
end
end
end
end
This is just loading in a yml file and refreshing some default values. Simple enough but I want it tested.
Here is what I came up with:
[spec/lib/tasks/app.rake_spec.rb]
require "/path/to/spec_helper"
require "rake"
describe "app rake tasks" do
before do
@rake = Rake::Application.new
Rake.application = @rake
Rake.application.rake_require "lib/tasks/app"
Rake::Task.define_task(:environment)
end
describe "rake app:options:refresh" do
before do
@task_name = "app:options:refresh"
YAML.stub!(:load_file).and_return([])
end
it "should have 'environment' as a prereq" do
@rake[@task_name].prerequisites.should include("environment")
end
it "should load 'config/options.yml'" do
YAML.should_receive(:load_file).with("config/options.yml").and_return([])
@rake[@task_name].invoke
end
it "should create or update all records in the config file" do
YAML.should_receive(:load_file).with("config/options.yml").and_return([
{ "name" => "option one", "value" => 10 },
{ "name" => "option two", "value" => 20 }
])
option_one = mock(Option, :null_object => true)
option_two = mock(Option, :null_object => true)
Option.should_receive(:find_or_initialize_by_name).with("option one").and_return(option_one)
Option.should_receive(:find_or_initialize_by_name).with("option two").and_return(option_two)
option_one.should_receive(:update_attribute).with("value", 10)
option_two.should_receive(:update_attribute).with("value", 20)
@rake[@task_name].invoke
end
end
end
Let's walk through whats going on here...
@rake = Rake::Application.new Rake.application = @rake
This is initializing a new rake application object and setting the current Rake.application to it. This is done so that each test is run with a fresh application object.
Rake.application.rake_require "lib/tasks/app"
This is requiring the rake file with the task I will be testing.
Rake::Task.define_task(:environment)
This is essentially stubbing the environment task which loads the rails environment. The rails env is already loaded when you are running your specs so this is unnecesary.
@rake[@task_name].invoke
This is what actually runs the rake task.
The rest of the 'it' blocks are pretty self explanatory, testing that the task has the correct prereqs and then just spec'ing the functionality.

7 comments:
thanks! This is what I was looking for.
I've been looking for this for a long time but too lazy to figure it out on my own- thanks a ton!
When I run my spec, any examples after the first one give me an error "Don't know how to build task"
An alternative is to move the logic/behaviour into some klass.method, spec that class/method in the usual way.... this simpliifes the spec file somewhat.
Then invoke the method in the rake task which becomes one line. HTH?
Hi,
Firstly I have to say if you have any control over your post-comment page it could use some love. Your original post is near unreadable here which makes it hard to reference when commenting.
I was going to say, a more realistic but probably less efficient way of doing this is to setup the @rake object with:
@rake = Rake.application
@rake.init
@rake.load_rakefile
The only thing then is if a rake task has already been invoked once it won't run again unless you call:
@rake[@task_name].reenable
or invoke it with
@rake[@task_name].execute
Having done the above myself though, hedge's approach is actually probably the cleanest, though it would discourage you from using rake dependencies to compose complex tasks out of subtasks.
Anthony, a little late but try this. It worked great for me.
http://stackoverflow.com/questions/1255176/test-rake-tasks/5306013#5306013
I had problems runing multiple tests in one describe block until added:
after { @rake[@task_name].clear }
Post a Comment