Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(apparently) identical tests for two rake tasks; only one passes

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)>'
like image 568
gregates Avatar asked Sep 08 '12 22:09

gregates


People also ask

What is rake test?

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.

How do rake tasks work?

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.

Where are Rake tasks defined?

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.


1 Answers

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.

like image 119
Dave Newton Avatar answered Sep 23 '22 09:09

Dave Newton