diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 20bf7e556..95b2ee625 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -75,6 +75,7 @@ RSpec.configure do |config| config.include Devise::Test::IntegrationHelpers, type: :request config.include FactoryBot::Syntax::Methods config.include OmniauthMacros + config.include SidekiqTestHelpers config.before do ActiveRecord::Base.observers.disable :all # <-- Turn 'em all off! diff --git a/spec/support/sidekiq_test_helpers.rb b/spec/support/sidekiq_test_helpers.rb new file mode 100644 index 000000000..d10c26f71 --- /dev/null +++ b/spec/support/sidekiq_test_helpers.rb @@ -0,0 +1,115 @@ +# Helpers for Sidekiq tests +# modeled after +# NOTE: contains code adapted from +module SidekiqTestHelpers + # Provides a store of all the enqueued jobs + def sidekiq_enqueued_jobs(queue: nil) + return Sidekiq::Queues[queue.to_s] if queue + + Sidekiq::Worker.jobs + end + + # Asserts that the number of enqueued jobs matches the given number. + # see + def sidekiq_assert_enqueued_jobs(number, only: nil, except: nil, queue: nil) + if block_given? + original_count = Utils.enqueued_jobs_size(only: only, except: except, queue: queue) + + yield + new_count = Utils.enqueued_jobs_size(only: only, except: except, queue: queue) + + error_message = "#{number} jobs expected, but #{new_count - original_count} were enqueued" + expect(new_count - original_count).to eq(number), error_message + else + actual_count = Utils.enqueued_jobs_size(only: only, except: except, queue: queue) + + expect(actual_count).to eq(number), "#{number} jobs expected, but #{actual_count} were enqueued" + end + end + + # Asserts that the job passed in the block has been enqueued with the given arguments. + # see + def sidekiq_assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil) + expected = { job: job, args: args, at: at, queue: queue }.compact + expected_args = Utils.prepare_args(expected) + + yield + + # check there's at least one job with the given args + matching_job = job.jobs.detect do |queued_job| + expected_args.all? { |key, value| value == queued_job[key] } + end + + expect(matching_job).to be_present, "No enqueued job found with #{expected}" + end + + # Asserts that no jobs have been enqueued. + # see + def sidekiq_assert_no_enqueued_jobs(only: nil, except: nil, &block) + sidekiq_assert_enqueued_jobs(0, only: only, except: except, &block) + end + + # Performs all enqueued jobs. + # If a block is given, performs all of the jobs that were enqueued throughout the duration of the block. + # If a block is not given, performs all of the enqueued jobs up to this point in the test. + # see + def sidekiq_perform_enqueued_jobs(only: nil, except: nil) + Utils.validate_option(only: only, except: except) + + if block_given? + jobs_enqueued_before_block = sidekiq_enqueued_jobs + + yield + + jobs_to_perform = sidekiq_enqueued_jobs - jobs_enqueued_before_block + + Utils.drain_jobs(jobs_to_perform, only: only, except: except) + else + Utils.drain_jobs(sidekiq_enqueued_jobs, only: only, except: except) + end + end + + class Utils + class << self + def drain_jobs(jobs, only: nil, except: nil) + jobs_to_perform = jobs + jobs_to_perform = jobs_to_perform.filter { |j| j["class"] == only.to_s } if only + jobs_to_perform = jobs_to_perform.reject { |j| j["class"] == except.to_s } if except + + jobs_to_perform.each do |job| + Sidekiq::Queues.delete_for(job["jid"], job["queue"], job["class"]) + job["class"].constantize.process_job(job) + end + end + + def enqueued_jobs_size(only: nil, except: nil, queue: nil) + validate_option(only: only, except: except) + + Sidekiq::Worker.jobs.count do |job| + job_class = job.fetch("class") + if only + next false unless Array(only).include?(job_class.constantize) + elsif except + next false if Array(except).include?(job_class.constantize) + end + if queue + next false unless queue.to_s == job.fetch("queue") + end + true + end + end + + def validate_option(only: nil, except: nil) + raise ArgumentError, "Cannot specify both `:only` and `:except` options." if only && except + end + + def prepare_args(args) + args.dup.tap do |arguments| + arguments[:at] = arguments[:at].to_f if arguments[:at] + arguments[:class] = arguments[:job].to_s + arguments.delete(:job) + end.with_indifferent_access + end + end + end +end diff --git a/spec/workers/rate_limit_checker_worker_spec.rb b/spec/workers/rate_limit_checker_worker_spec.rb index fcb6ab046..f2b76b2d7 100644 --- a/spec/workers/rate_limit_checker_worker_spec.rb +++ b/spec/workers/rate_limit_checker_worker_spec.rb @@ -1,6 +1,14 @@ require "rails_helper" RSpec.describe RateLimitCheckerWorker, type: :worker do + describe "#perform_async" do + it "enqueues a job correctly" do + sidekiq_assert_enqueued_with(job: described_class, args: [1, "test"], queue: "default") do + described_class.perform_async(1, "test") + end + end + end + describe "#perform" do let(:user) { create(:user) } let(:service) { PingAdmins } @@ -9,19 +17,15 @@ RSpec.describe RateLimitCheckerWorker, type: :worker do before { allow(service).to receive(:call) } it "calls a service" do - perform_enqueued_jobs do - worker.perform(user.id, "test") + worker.perform(user.id, "test") - expect(service).to have_received(:call).with(user, "test").once - end + expect(service).to have_received(:call).with(user, "test").once end it "does nothing for non-existent user" do - perform_enqueued_jobs do - worker.perform(nil, "test") + worker.perform(nil, "test") - expect(service).not_to have_received(:call) - end + expect(service).not_to have_received(:call) end end end