I'm trying to write tests in rspec for two rake tasks which are defined in the same file (in a Rails 3.0.11 project). For some reason only one of them passes. I've written a small demo to abstract away the actual content of the tasks, and the same thing happens. Both tasks work when invoked with rake
from the command line. What's going on? Here's my demo:
lib/tasks/demo_tasks.rake
namespace :demo do
task :test => :environment do
puts "test!"
end
task :test_two => :environment do
puts "second test!"
end
end
spec/lib/tasks/demo_spec.rb
require 'spec_helper'
require 'rake'
describe "test tasks" do
let(:rake) do
app = Rake::Application.new
app.options.silent = true
app
end
before :each do
Rake.application = rake
Rake.application.rake_require 'lib/tasks/demo_tasks',
[Rails.root.to_s]
Rake::Task.define_task :environment
end
describe "demo:test" do
it "runs" do
rake["demo:test"].invoke
end
end
describe "demo:test_two" do
it "also_runs" do
rake["demo:test_two"].invoke
end
end
end
rspec spec/lib/tasks/demo_spec.rb
test tasks
demo:test
test!
runs
demo:test_two
also_runs (FAILED - 1)
Failures:
1) test tasks demo:test_two also_runs
Failure/Error: rake["demo:test_two"].invoke
RuntimeError:
Don't know how to build task 'demo:test_two'
# ./spec/lib/tasks/demo_spec.rb:26:in `block (3 levels) in <top (required)>'
Rake is a popular task runner for Ruby and Rails applications. For example, Rails provides the predefined Rake tasks for creating databases, running migrations, and performing tests. You can also create custom tasks to automate specific actions - run code analysis tools, backup databases, and so on.
These are small tasks that without Rake would be scattered all over your project on different files. Rake centralizes access to your tasks. Rake also makes a few things easier, like finding files that match a certain pattern & that have been modified recently.
When we are talking about the task db:migrate for example, it is located within the rails gem in lib/tasks/databases.rake. So for a specific project, you will always have the tasks within the project folder structure as well as all tasks within the specified gems.
Nutshell: Change your before
to a before :all
(instead of :each
).
Or: Pass an empty array as a third parameter to rake_require
.
Rake.application.rake_require 'lib/tasks/demo_tasks',
[Rails.root.to_s],
[]
Details
def rake_require(file_name, paths=$LOAD_PATH, loaded=$")
fn = file_name + ".rake"
return false if loaded.include?(fn)
...
$"
is a Ruby special variable that holds an array of modules loaded by require
.
If you don't pass the optional parameter, rake_require
will use that array of modules loaded by Ruby. This means the module won't be loaded again: Ruby knows the module was loaded, rake checks to see what Ruby knows, and it's a new rake instance for each test.
Switching to before :all
worked because it meant the let
block only ran once: one rake instance, one module load, everybody is happy.
All this said, why reload the rake environment twice anyway? Your goal is to test your tasks, which doesn't require a fresh rake context for every spec.
You could eliminate the local altogether at the cost of some minor verbosity in each spec:
describe "test tasks" do
before :all do
Rake.application = Rake::Application.new
Rake.application.rake_require 'lib/tasks/demo_tasks', [Rails.root.to_s]
Rake::Task.define_task :environment
end
describe "demo:test" do
it "runs" do
Rake::Task["demo:test"].invoke
end
end
end
You could define an instance variable in the before
block to avoid the Rake::Task
reference:
before :all do
@rake = Rake::Application.new
Rake.application = @rake
Rake.application.rake_require 'lib/tasks/demo_tasks', [Rails.root.to_s]
Rake::Task.define_task :environment
end
describe "demo:test" do
it "runs" do
@rake["demo:test"].invoke
IMO, less desirable for a number of reasons. Here's a summary I agree with.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With