Test your Rake tasks!

February 5, 2012 Stephan Hagemann

There are several reasons why you should test your Rake tasks:

  • Rake tasks are code and as such deserve testing.
  • When untested Rake tasks have a tendency to become overly long and convoluted. Tests will help keep them in bay.
  • As Rake tasks typically depend on your models, you (should) loose confidence in them if you don’t have tests and are attempting refactorings.

A problematic Rake task test

Here is a Rake file…

File: lib/tasks/bar_problematic.rake

namespace :foo do
  desc "bake some bars"
  task bake_a_problematic_bar: :environment do
    puts '*' * 60
    puts ' Step back: baking in action!'
    puts '*' * 60

    puts Bar.new.bake

    puts '*' * 60
    puts ' All done. Thank you for your patience.'
    puts '*' * 60
  end
end

…and its too simplistic spec:

File: spec/tasks/bar_rake_problematic_spec.rb
require 'spec_helper'
require 'rake'

describe 'foo namespace rake task' do
  describe 'foo:bake_a_problematic_bar' do

    before do
      load File.expand_path("../../../lib/tasks/bar_problematic.rake", __FILE__)
      Rake::Task.define_task(:environment)
    end

    it "should bake a bar" do
      Bar.any_instance.should_receive :bake
      Rake::Task["foo:bake_a_problematic_bar"].invoke
    end

    it "should bake a bar again" do
      Bar.any_instance.should_receive :bake
      Rake::Task["foo:bake_a_problematic_bar"].invoke
    end
  end
end

Some notable aspects of testing Rake tasks:

  • Rake has to be required.
  • The Rake file under test has to be manually loaded.
  • In this example, the Rake task depends on the environment task, which is not automatically available in a spec. Since we are in rspec, the environment is already loaded and we can just define environment as an empty Rake task to make the bake task run in the test.

When run, this spec fails on the second it block… and that is not the only problem with this spec and the Rake task:

  • The Rake task duplicates code to output information to the user.
  • The spec “should bake a bar” will output that information when run, which clobbers the spec runners output.
  • The spec “should bake a bar” again will fail, because Rake tasks are built to only execute once per process. See rake.rb. This makes sense for the normal use of Rake tasks where a task may be named as the prerequisite of another task multiple times through multiple dependencies it might have – the task only needs to run once. In our tests we have to reenable the task.

A better Rake task test

A new version of the above Rake file…

File: lib/tasks/bar.rake
class BarOutput
  def self.banner text
    puts '*' * 60
    puts " #{text}"
    puts '*' * 60
  end

  def self.puts string
    puts string
  end
end

namespace :foo do
  desc "bake some bars"
  task bake_a_bar: :environment do
    BarOutput.banner " Step back: baking in action!"
    BarOutput.puts Bar.new.bake
    BarOutput.banner " All done. Thank you for your patience."
  end
end

… and its spec:

File: spec/tasks/bar_rake_spec.rb
require 'spec_helper'
require 'rake'

describe 'foo namespace rake task' do
  before :all do
    Rake.application.rake_require "tasks/bar"
    Rake::Task.define_task(:environment)
  end

  describe 'foo:bar' do
    before do
      BarOutput.stub(:banner)
      BarOutput.stub(:puts)
    end

    let :run_rake_task do
      Rake::Task["foo:bake_a_bar"].reenable
      Rake.application.invoke_task "foo:bake_a_bar"
    end

    it "should bake a bar" do
      Bar.any_instance.should_receive :bake
      run_rake_task
    end

    it "should bake a bar again" do
      Bar.any_instance.should_receive :bake
      run_rake_task
    end

    it "should output two banners" do
      BarOutput.should_receive(:banner).twice
      run_rake_task
    end

  end
end

This spec passes just fine and does not clobber the spec output. Again, let’s look at noteworthy things:

  • The output of the Rake task now goes through the BarOutput class. This reduces code duplication and allows for easy stubbing. There are other ways to achieve a similar effect and not clobber test output: Stub puts and print, stub on $stdout.
  • Rake.application has a nicer way of requiring Rake files than a simple load, because rake_require knows where Rake files live.
  • Rake::Task["TASK"].reenable reenables the task with name “TASK” so that it will be run again and can be called multiple times in a spec.

Here is the gist: https://gist.github.com/1764423

About the Author

Biography

Previous
IntelliJ Modules in Rubymine
IntelliJ Modules in Rubymine

IntelliJ has a feature called modules: "a functional unit which you can compile, run, test and debug indepe...

Next
New in Pivotal Tracker: Improved Stories!
New in Pivotal Tracker: Improved Stories!

Stories in Pivotal Tracker have been given a serious upgrade. For the most part it’s all pretty self explan...

×

Subscribe to our Newsletter

!
Thank you!
Error - something went wrong!