Testing Rake Tasks in Rails
I recently wrote a rake task to update some values we’ve got stored in the database. The rake task itself isn’t important in this post, but testing it is.
We’ve got many untested rake tasks in the database, so when our senior dev suggested adding a test, I had to build ours from scratch.
I did a bit more whack-a-moling with error messages than I’d hoped, so here’s a template of that test, along with some details that might save you some time, next time you are writing tests for your rake tasks.
We’re in a not-new version of Rails, and using Minitest. I’ve anonymized it. Hope it’s useful!
# test/tasks/rake_task_file_test.rb
require 'test_helper'
require 'rake'
class RakeTaskFileTaskTest < ActiveSupport::TestCase
describe 'namespace:task_name' do
def setup
@tt = Fabricate(:object_with_attributes_i_need_to_change)
YourApplicationName::Application.load_tasks if Rake::Task.tasks.empty?
Rake::Task["namespace:task_name"].invoke
end
it "should change 'thing I don't want'" do
@tt.reload
values = @tt.attribute_i_changed
refute_includes values, "thing I don't want"
assert_includes values, "thing I do want"
end
end
end
Notes on the above:
require 'rake'
- I was getting aNoMethodError: undefined method 'namespace' for main:Object
until adding this line. Found the answer in an unrelated-ish github issue, of course.- The rake task reads
@tt.attribute_i_changed
, does logic on it, and then changes the value. The object I was changing had quite a few dependencies on other objects, so I just copied an existing factory, changed the values as needed, and called that factory in theinitialization
method. ApplicationName::Application.load_tasks
makes all the rake tasks available inside this test. Without.load_tasks
, nothing else works. Without `if Rake::Task.tasks.empty?` makes sure it loads them only if they’re not currently loaded. (Thanks @Ratanachai Ken Sombat)Rake::Task["namespace:task_name"].invoke
runs the task under test.@tt.reload
is very important. It’s obvious in hindsight, but since the rake task modified values of@tt
, I have to reload it from the database. Otherwise, the test has no idea the values changed when I call@tt.attributes_i_changed
- And a few standard
refute_includes
andassert_includes
, and we’re on our way.
Nov 2023 update
In the comments below, Nick Gobin kindly pointed out some complexity around testing the task multiple times. Your milage may vary, but regardless if you’re having trouble ‘getting’ to the Rake task in your test, one of these two ideas should get you moving. Nick’s or Ratanachai’s solution.
Only “gotcha” that I encountered that I would add is that you need to “reenable” the task each time if you want to use it multiple times in a spec:”
Rake::Task['mytask'].reenable
Additionally, from 2019, Ratanachai Ken Sombat proposed perhaps a similar-but-different solution to the same problem. Thank you Ratanachai!
however, the test will be run twice (which in my case would fail).
I fixed it by
ApplicationName::Application.load_tasks if Rake::Task.tasks.empty?
Useful resources
- Thoughtbot: Test Rake Tasks Like a BOSS
- Ilija Eftimov: Why and how to test Rake tasks in your Rails application
- Pivotal blog: Test your Rake tasks!
This article was originally posted on my own site.