How to Cleanly Test Rails Rake Tasks with RSpec

When you need to test Rake tasks for a Rails app, its convenient to be able to write short, snappy specs like so:

# File: spec/tasks/send_invoices_spec.rb
require "rails_helper"

describe "rake billing:send_invoices", type: :task do

  it "preloads the Rails environment" do
    expect(task.prerequisites).to include "environment"
  end

  it "runs gracefully with no subscribers" do
    expect { task.execute }.not_to raise_error
  end

  it "logs to stdout" do
    expect { task.execute }.to output("Sending invoices\n").to_stdout
  end

  it "emails invoices" do
    subscriber = create(:subscriber)

    task.execute

    expect(subscriber).to have_received_invoice
  end

  it "checks in with Dead Mans Snitch" do
    dead_mans_snitch_request = stub_request(:get, "https://nosnch.in/c2354d53d2")

    task.execute

    expect(dead_mans_snitch_request).to have_been_requested
  end

  matcher :have_received_invoice do
    match_unless_raises do |subscriber|
      expect(last_email_sent).to be_delivered_to subscriber.email
      expect(last_email_sent).to have_subject 'Your invoice'
      ...
    end
  end

end

To be able to do this for your project, create the following file spec/support/tasks.rb (ensuring its require-d by rails_helper.rb or spec_helper.rb):

# File: spec/support/tasks.rb
require "rake"

# Task names should be used in the top-level describe, with an optional
# "rake "-prefix for better documentation. Both of these will work:
#
# 1) describe "foo:bar" do ... end
#
# 2) describe "rake foo:bar" do ... end
#
# Favor including "rake "-prefix as in the 2nd example above as it produces
# doc output that makes it clear a rake task is under test and how it is
# invoked.
module TaskExampleGroup
  extend ActiveSupport::Concern

  included do
    let(:task_name) { self.class.top_level_description.sub(/\Arake /, "") }
    let(:tasks) { Rake::Task }

    # Make the Rake task available as `task` in your examples:
    subject(:task) { tasks[task_name] }
  end
end


RSpec.configure do |config|

  # Tag Rake specs with `:task` metadata or put them in the spec/tasks dir
  config.define_derived_metadata(:file_path => %r{/spec/tasks/}) do |metadata|
    metadata[:type] = :task
  end

  config.include TaskExampleGroup, type: :task

  config.before(:suite) do
    Rails.application.load_tasks
  end
end

Source: https://github.com/eliotsykes/rails-testing-toolbox/blob/master/tasks.rb

Now write your task specs in the spec/tasks/ directory like the example send_invoices_spec.rb given earlier, in which you will be able to call:

  • task.execute to run the task
  • task.prerequisites to access the tasks that task depends on
  • task.invoke to run the task and its prerequisite tasks

Published by Eliot Sykes

I help teams grow their Rails apps and themselves as a Rails consultant and coach for Rails developers.