Testing Rake Tasks With RSpec and Fantaskspec

Rake is great, but sometimes it can be a little tricky to test. I’ve bundled together some convenient helpers for RSpec to make testing Rake tasks easier.

Setup

First install Fantaskspec by adding it to you Gemfile:

group :test do
  gem "fantaskspec"
end

Then bundle install as usual.

In your spec_helper.rb or rails_helper.rb you’ll need to require Fantaskspec:

require "fantaskspec"

Then you can make any test a Rake test by simply passing a type option to describe:

RSpec.describe "my_rake_task", type: :rake do
# tests!
end

I tend to make a spec file per task or namespace, depending on the size of the task(s).

Also, if you’d rather not add type: :rake to every group of Rake tests, you can tell RSpec to assume tests in spec/tasks and spec/lib/tasks are Rake specs by doing this:

RSpec.configure do |config|
  # ...
  config.infer_rake_task_specs_from_file_location!
  # ...
end

Loading Your Rake Tasks

If you’re using Rails, simply add this to your rails_helper.rb:

Rails.application.load_tasks

If you’re in a non-Rails Ruby project you can require "rake" and also require all of your task defining files in your spec_helper.rb.

Testing

The Task

Fantaskspec naturally gives us access to the task object as the subject and task:

# spec/tasks/a_great_task_spec.rb
RSpec.describe "a_great_task", type: :rake do
  it "rakes it out" do
    expect(subject).to be_a(Rake::Task)
    expect(subject.name).to eq("a_great_task")
    expect(subject).to eq(task)
  end
end

The task doesn’t need to be named in the outer most describe. You can nest them however you want and Fantaskspec will do the right thing.

# spec/tasks/some_task_spec.rb
RSpec.describe "some_task", type: :rake do
  it "is some_task here" do
    expect(subject.name).to eq("some_task")
  end

  context "other tasks" do
    context "some_other_task" do
      it "is some_other_task here" do
        expect(subject.name).to eq("some_other_task")
      end
    end
  end
end

If you don’t want to put the task name as the describe or context string you can always just let :task_name:

RSpec.describe "the task that does stuff", type: :rake do
  let(:task_name) { "do_stuff" }

  it "is do_stuff here" do
    expect(subject.name).to eq("do_stuff")
  end
end

Be sure to put the fully qualified task name in the describe, context, or task_name let.

RSpec.describe "a:namespaced:task", type: :rake do
  it "is super namespaced" do
    expect(task_name).to eq("a:namespaced:task")
  end
end

Running The Task

Just call execute on the task:

it "creates an admin user" do
  expect { subject.execute }.to change(AdminUser, :count).from(0).to(1)
end

Testing Dependencies

Fantaskspec gives us the handy depend_on matcher to assert our task’s dependencies:

it "depends on the Rails environment task" do
  expect(subject).to depend_on(:environment)
end
# or more concisely
it { is_expected.to depend_on(:environment) }

It’s also easy to enforce multiple dependencies and their order:

it { is_expected.to depend_on(:environment, "some:other_task") }

Wrapping Up

Armed with Fantaskspec’s helpers, test driving your next Rake task will be a breeze.

DevMynd is software development companies in San Francisco with practice areas in custom mobile and web application development.

Michael is a member of DevMynd’s software engineering team focusing on mobile apps and web development. He has been with the company since 2013.