testing rake tasks with Rspec is not accepting arguments
So, according to this and this, the following are some ways of calling rake tasks with arguments:
Rake.application.invoke_task("my_task[arguments]")
or
Rake::Task["my_task"].invoke(arguments)
On the other hand, I was calling the task as:
Rake::Task["my_task[arguments]"].invoke
Which was a Mis combination of the above two methods.
A big thank you
to Jason for his contribution and suggestion.
How to test rake task when arguments are passed
It's common practice in commands to use environment variables for configuration. You'll see this used in many different gems. For your needs, you could do something like this instead:
task :do_something_after_n_number_of_days do
raise ArgumentError, 'Invalid DAYS environment setting' if ENV['DAYS'].nil?
puts ENV['DAYS']
end
Then you can set the ENV
in your test, like this:
let(:task) { Rake::Task['do_something_after_n_number_of_days'] }
context "when DAYS is set" do
before { ENV['DAYS'] = '100' }
it "does something" do
expect { task.invoke }.to output("100").to_stdout
end
end
context "when DAYS is nil" do
before { ENV['DAYS'] = nil }
it "raises an ArgumentError" do
expect { task.invoke }.to raise_error(ArgumentError, /Invalid DAYS/)
end
end
How to run rake task with parameter in rspec
You can pass the arguments to invoke
:
Rake::Task["user_details:address"].invoke(user_id)
Rspec testing rake tasks
It turns out that when a rake task has been invoke
d it won't be ran again. You have to either reenable
the task after invocation or use execute
How to define a Rake task with arguments and dependencies
The answer is in a comment documenting the 'resolve_args_with_dependencies(args, hash)' method in the lib/rake/task_manager.rb file.
# The patterns recognized by this argument resolving function are:
#
# task :t => [:d]
# task :t, [a] => [:d]
So this means you need to declare the task as follows
# :spec task depends on clean and 'test-reports' tasks
# and takes args for tags and triggers
RSpec::Core::RakeTask.new(:spec, [:tag, :triggers] => [:clean, 'test-reports']) do |task, args|
task.rspec_opts = "--tag #{args[:tag]}"
# args is a Rake::TaskArguments object (NOT a hash)
ENV[TRIGGERS] = args[:triggers]
end
rake rspec test doesn't run
You are running the spec with ruby
, not rspec
. This is why you don't see any output, your test will run as if it is a regular ruby script.
Change your Rakefile
to run rspec
instead:
begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task :default => :spec
rescue LoadError
puts "RSpec is not installed!"
end
More details here.
UPDATE
If you want to pass parameters to rspec, you can do it like this:
RSpec::Core::RakeTask.new(:spec) do |t|
t.rspec_opts = "--format documentation"
end
This will run the spec with the format as documentation.
Off topic
When you are describing a class, best practices say you should pass the class to describe
method instead of a string. This way your code looks cleaner and rspec will take care of the instantiating it by itself (the instance will be available as subject
).
For example:
RSpec.describe FileReader do
context "with sample test input file" do
it "reads a file and prints its contents" do
expect(subject.input_file('./files/test_input.txt')).to eq ('file text')
end
end
end
Running a Rake task with parameters
As I mentioned in a comment, the task isn't being invoked from the test because of the way you're stubbing here:
expect(Rake::Task['myapp:seed:all']).to receive(:invoke)
Although this checks whether invoke
was called, it doesn't actually invoke invoke
(actually, it makes the method return nil). To change that, you can either:
- tack on an
and_return(<something>)
- tack on
and_call_original
.
Probably in this case you'd want to use and_call_original
since you want to investigate what actually happens in the task. In order to stub individual method calls in the task, the approach you have been using (expect_any_instance_of(Object).to receive(:system)
) will technically work, but could probably be refactored to be more decoupled from the code.
For example, you could separate each system
call into its own method (available to the rake task), and then call those from the test. Then in order to stub it you only need to pass the method name. If you want, you can then go and unit test each of those methods individually, putting the system
call expectation in there.
I don't recall where exactly but I've heard it advised to not do any acual programming in Rake tasks. Put your code somewhere in your regular codebase, and call those methods from the rake task. This can be seen as an example of a more general pattern which is to refactor large methods into smaller ones. Writing code this way (and also with a functional style, but I won't get into that) makes your life easier when testing.
onto your followup question:
as you can see in the test case's failure message, the only difference between the actual and expected is that one is a regex and the other is a string.
A simple fix for this is to change this line:
expect_any_instance_of(Object).to receive(:system).with(/RAILS_ENV=testing rake db:drop/).and_return(true)
so that the with()
argument is a string, not a regex
Related Topics
Does Anyone Have Parsing Rules for the Notepad++ Function List Plugin for Ruby and Rake
How to Simplify or Clean Up This Anagram Method
How to Get a Reference to a Method
Minitest, Test::Unit, and Rails
Bundle Uses Wrong Ruby Version
How to Get a Listing of Only Files Using Dir.Glob
How to Get Today's Date in Ruby 1.9.3
Ruby: "If !Object.Nil" or "If Object"
How to Set a Proxy in Rubys Net/Http
How to Upload a JSON File with Secret Keys to Heroku
Parsing Large Xml with Nokogiri
How to Create an Operator for Deep Copy/Cloning of Objects in Ruby
How to Include Unit Tests in a Ruby Module
What's the Best Way to Return an Enumerator::Lazy When Your Class Doesn't Define #Each