codahale.com٭blog

Coda Hale lives in Berkeley, CA, where he writes about Ruby on Rails, usability, web design and development, and the occasional bit about bicycles.

Rake vs. RSpec! Fight!

I love RSpec, and lately I’ve been making the transition from test-friendly development to full-on spec-driven development. I still toss around some code for proofs of concept or to prototype APIs, but when the time comes to write serious code, I always begin with a spec.

I was working on a project recently which boiled down to “run these tasks in this order,” which is a natural fit for Rake. I have lots of beef with Rake, but I was able to back away from the yak-shaving precipice with this:

def describe_rake_task(task_name, filename, &block)
  require "rake"

  describe "Rake task #{task_name}" do
    attr_reader :task

    before(:all) do
      @rake = Rake::Application.new
      Rake.application = @rake
      load filename
      @task = Rake::Task[task_name]
    end

    after(:all) do
      Rake.application = nil
    end

    def invoke!
      for action in task.instance_eval { @actions }
        instance_eval(&action)
      end
    end

    instance_eval(&block)
  end
end

Drop that in your spec_helper.rb and you can do stuff like this:

describe_rake_task "build:my_thing", "lib/tasks/my_thing.rake" do

  it "should build my other thing first" do
    task.prerequisites.should include("build:my_other_thing")
  end

  it "should do something" do
    @built_my_thing = false
    Builder.should_receive(:build).with(:my_thing)
    invoke!
    @built_my_thing.should be(false)
  end

end

Which, when run, produces this:

Rake task build:my_thing
- should build my other thing first
- should do something

The invoke! method runs the task’s action(s) inside the spec’s instance, which means you can mock out methods and handle changes to instance variables.

Doesn’t much change my opinion of Rake, but at least I’ve got a better way to write tasks for it.

4 comments »