Okay, let's be honest, when was the last time you actually wrote a unit test for a Rake task? My guess is NEVER.

Unit testing Rake tasks is a pain in the ass to say the least. Most developers "work around" unit testing Rake tasks by extracting logic out of the Rake task and into an actual unit testable Ruby object, still leaving the actual Rake task untested. This may be acceptable for some developers, but any technology that is prohibitively difficult to test should be a red flag that something is wrong. This also can leave significant coverage gaps if your Rake task is parameterized and requires any parsing of CLI options.

Now, if you extract logic out of your Rake task and into a good old testable Ruby object, why is the extra boilerplate (and untested) Rake task even necessary? Let's get rid of that extra layer of indirection and complexity and simply use Thor instead.

The beauty of Thor tasks is that they are Plain Old Ruby Objects and a Rake-like task runner all in one. Your tasks are now 100% unit testable without jumping through hoops, and your code is consolidated into one easily maintainable location.

Go from this...

# Rakefile
task :do_something do
  MyCommand.new.run
end

# lib/my_command.rb
class MyCommand
  def run
    # do something here
  end
end

To this...

# lib/tasks/my_command.thor
class MyCommand < Thor
  desc 'do_something', 'do some work'
  def do_something
    # do something here
  end
end

Seriously, it's that easy. Life on the Thor bandwagon is great from here on out. You'll never look back at Rake...until...

Rails Environment Dependencies

Undoubtedly, there will come a time where you will want to write a Thor task that relies on your Rails environment. Seems simple right? Good old Rake made it easy:

task :do_something => :environment do
  # Rails environment is loaded
end

So, how do you do this in Thor? The thor-rails gem is my recommended solution. Include the Thor::Rails module into your Thor command, and the Rails environment will automatically be loaded just like the Rake :environment dependency.

class MyCommand < Thor
  include Thor::Rails

  desc 'do_something', 'do some work'
  def do_something
    # Rails environment is loaded and available!
    say Rails.env
  end
end

Gem Extensions

Rake has been around for a long time, and the Ruby community has built a considerable collection of extensions and integrations. There were two critical features that needed to be addressed in order to complete the migration from Rake to Thor...

Performance Monitoring (NewRelic)

NewRelic is my goto solution for application profiling and the newrelic-rake gem has been an excellent addition to introspect performance of Rake tasks.

The newrelic-thor gem brings that same profiling support to your Thor tasks with no code changes. Can't get much easier then that!

Exception Tracking (Honeybadger)

Honyebadger is an excellent solution for tracking application exceptions. They even package tracking of Rake exceptions right into their core gem.

The honeybadger-thor gem adds similar exception tracking to your Thor tasks. Just drop the gem into your app and you're set! Hopefully, this feature will be integrated into the core honeybadger gem sometime soon.

There's simply no excuse to not write tests for your code, and Rake tasks are most definitely code. But instead of delving into the depths and horrors of how to unit test Rake tasks, my answer is to simply leave your Rake in the yard and use Thor.

Ryan Sonnek is...

A passionate web developer focused on launching Lean startups using Ruby and Javascript technologies.
comments powered by Disqus