While simple coverage is reporting this as 100% covered I am not satisfied. The spec marked as focus I would like to confirm that all of the Resque methods are being fired. Is a spy or a double the right approach for this?
Spec
describe 'resque tasks' do
include_context 'rake'
let(:task_paths) { ['tasks/resque'] }
before do
invoke_task.reenable
end
# rubocop:disable all
describe 'resque' do
context ':setup' do
let(:task_name) { 'resque:setup' }
it 'works' do
invoke_task.invoke
expect(Resque.logger.level).to eq(1)
end
end
context ':scheduler_setup' do
let(:task_name) { 'resque:scheduler_setup' }
it 'works' do
expect(invoke_task.invoke).to be
end
end
context ':clear', focus: true do
let(:task_name) { 'resque:clear' }
it 'works' do
expect(Resque).to receive(:remove_queue).with('queue:default').and_return(true)
expect { invoke_task.invoke }.to output(
"Clearing default...\n"\
"Clearing delayed...\n"\
"Clearing stats...\n"\
"Clearing zombie workers...\n"\
"Clearing failed jobs...\n"\
"Clearing resque workers...\n"
).to_stdout
end
end
end
describe 'jobs:work' do
let(:task_name) { 'jobs:work' }
it 'works' do
expect_any_instance_of(Object).to receive(:system).with("bundle exec env rake resque:workers QUEUE='*' COUNT='#{ENV['WEB_WORKERS']}'").and_return(true)
expect(invoke_task.invoke).to be
end
end
# rubocop:enable all
end
Resque Rake Task
require 'resque'
require 'resque/tasks'
require 'resque/scheduler/tasks'
# http://jademind.com/blog/posts/enable-immediate-log-messages-of-resque-workers/
namespace :resque do
desc 'Initialize Resque environment'
task setup: :environment do
ENV['QUEUE'] ||= '*'
Resque.logger.level = Logger::INFO
end
task scheduler_setup: :environment
# see http://stackoverflow.com/questions/5880962/how-to-destroy-jobs-enqueued-by-resque-workers - old version
# see https://github.com/defunkt/resque/issues/49
# see http://redis.io/commands - new commands
desc 'Clear pending tasks'
task clear: :environment do
queues = Resque.queues
queues.each do |queue_name|
puts "Clearing #{queue_name}..."
Resque.remove_queue("queue:#{queue_name}")
end
puts 'Clearing delayed...'
Resque.redis.keys('delayed:*').each do |key|
Resque.redis.del key.to_s
end
Resque.redis.del 'delayed_queue_schedule'
Resque.reset_delayed_queue
puts 'Clearing stats...'
Resque.redis.set 'stat:failed', 0
Resque.redis.set 'stat:processed', 0
puts 'Clearing zombie workers...'
Resque.workers.each(&:prune_dead_workers)
puts 'Clearing failed jobs...'
cleaner = Resque::Plugins::ResqueCleaner.new
cleaner.clear
puts 'Clearing resque workers...'
Resque.workers.each(&:unregister_worker)
end
end
desc 'Alias for resque:work'
# http://stackoverflow.com/questions/10424087/resque-multiple-workers-in-development-mode
task 'jobs:work' do
system("bundle exec env rake resque:workers QUEUE='*' COUNT='#{ENV['WEB_WORKERS']}'")
end
Shared Context
shared_context 'rake' do
let(:invoke_task) { Rake.application[task_name] }
let(:highline) { instance_double(HighLine) }
before do
task_paths.each do |task_path|
Rake.application.rake_require(task_path)
end
Rake::Task.define_task(:environment)
end
before do
allow(HighLine).to receive(:new).and_return(highline)
end
end
The spec marked as focus I would like to confirm that all of the Resque methods are being fired. Is a spy or a double the right approach for this?
Yes. A Spy in this test would only be testing that it received those methods calls, since it is acting as a double
stand-in for those tests; meaning you are not testing the behaviour of task in this test, you are testing that the task has an object like Resque
receiving those method calls.
Spies
Message expectations put an example's expectation at the start, before you've invoked the code-under-test. Many developers prefer using an act-arrange-assert (or given-when-then) pattern for structuring tests. Spies are an alternate type of test double that support this pattern by allowing you to expect that a message has been received after the fact, using
have_received
.
An example of what this might look like for your it 'works'
test
it 'works' do
expect(Resque).to receive(:remove_queue).with('queue:default').and_return(true)
expect { invoke_task.invoke }.to output(
"Clearing default...\n"\
"Clearing delayed...\n"\
"Clearing stats...\n"\
"Clearing zombie workers...\n"\
"Clearing failed jobs...\n"\
"Clearing resque workers...\n"
).to_stdout
end
Is as follows
RSpec.describe "have_received" do
it 'works' do
Rake::Task.define_task(:environment)
invoke_task = Rake.application['resque:clear']
redis_double = double("redis")
allow(redis_double).to receive(:keys).with('delayed:*').and_return([])
allow(redis_double).to receive(:del).with('delayed_queue_schedule').and_return(true)
allow(redis_double).to receive(:set).with('stat:failed', 0).and_return(true)
allow(redis_double).to receive(:set).with('stat:processed', 0).and_return(true)
allow(Resque).to receive(:queues).and_return([])
allow(Resque).to receive(:redis).and_return(redis_double)
# allow(Resque).to receive(:remove_queue).with('queue:default') #.and_return(true)
allow(Resque).to receive(:reset_delayed_queue) #.and_return(true)
allow(Resque).to receive(:workers).and_return([])
cleaner_double = double("cleaner")
allow(Resque::Plugins::ResqueCleaner).to receive(:new).and_return(cleaner_double)
allow(cleaner_double).to receive(:clear).and_return(true)
expect { invoke_task.invoke }.to output(
# "Clearing default...\n"\
"Clearing delayed...\n"\
"Clearing stats...\n"\
"Clearing zombie workers...\n"\
"Clearing failed jobs...\n"\
"Clearing resque workers...\n"
).to_stdout
expect(redis_double).to have_received(:keys)
expect(redis_double).to have_received(:del)
expect(redis_double).to have_received(:set).with('stat:failed', 0)
expect(redis_double).to have_received(:set).with('stat:processed', 0)
expect(Resque).to have_received(:queues)
expect(Resque).to have_received(:redis).at_least(4).times
# expect(Resque).to have_received(:remove_queue).with('queue:default')
expect(Resque).to have_received(:reset_delayed_queue)
expect(Resque).to have_received(:workers).twice
expect(Resque::Plugins::ResqueCleaner).to have_received(:new)
expect(cleaner_double).to have_received(:clear)
end
end
Notes:
The allow(Resque).to receive(:remove_queue).with('queue:default')
is commented out since allow(redis_double).to receive(:keys).with('delayed:*').and_return([])
returns an empty array in my example code, meaning that queues.each
never iterates once, so Resque.remove_queue("queue:#{queue_name}")
is never called and "Clearing default...\n"\
is not return for the expected output
Also, there is a lot happening in this one task, and might be worthwhile breaking it down into smaller tasks.
This effectively stubs each of the expected method calls on the Resque
object and then accesses after task has been invoked that the doubles receive those expected method calls. It does not test the outcomes of those tasks, only that method calls occurred and confirms those
methods are being fired.
References: