Testing Rake Tasks with Rspec Is Not Accepting Arguments

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 invoked 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:

  1. tack on an and_return(<something>)
  2. 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



Leave a reply



Submit