调用Sidekiq::Job.jobs方法触发NoMethodError,寻求可测试的任务删除方案
调用Sidekiq::Job.jobs方法触发NoMethodError,寻求可测试的任务删除方案
问题原因分析
你遇到的NoMethodError其实很好理解:Sidekiq::Job模块本身并没有提供jobs这个方法。之前你可能误以为Sidekiq::Worker.jobs可以直接迁移到Sidekiq::Job,但实际上Sidekiq::Worker.jobs是Sidekiq测试模式下才会注入的模拟方法——它只用来在测试环境里查看、操作模拟的任务队列,完全不是生产环境的正式API。
下面分场景给你提供可落地的解决方案,同时兼顾测试需求:
一、生产/实际运行环境的正确删除方式
如果要删除已调度的任务(比如在ScheduledSet或RetrySet里的任务),必须用Sidekiq官方提供的集合类来操作,具体修改你的cancel_notification方法即可:
private def cancel_notification return if event.notification_job_id.blank? # 遍历所有可能存储任务的集合:调度中、重试中的任务都覆盖到 [Sidekiq::ScheduledSet.new, Sidekiq::RetrySet.new].each do |set| set.each do |job| job.delete if job.jid == event.notification_job_id end end end
二、测试环境的可测试方案
如果你觉得直接操作ScheduledSet不好写测试,可以通过抽象依赖+利用Sidekiq测试模式API来解决,步骤如下:
1. 重构服务代码,抽象任务删除逻辑
把Sidekiq相关的操作抽成单独的管理类,这样测试时可以轻松mock或者替换实现:
# frozen_string_literal: true module Api module V1 module Events class Destroy < BaseService attr_reader :event, :sidekiq_task_manager # 注入任务管理依赖,默认用真实实现,测试时可以传mock def initialize(event:, sidekiq_task_manager: SidekiqTaskManager.new) @event = event @sidekiq_task_manager = sidekiq_task_manager end def call cancel_notification event.destroy success!(event) end private def cancel_notification return if event.notification_job_id.blank? sidekiq_task_manager.delete_job_by_jid(event.notification_job_id) end end end end end # 单独封装Sidekiq任务操作的类 class SidekiqTaskManager def delete_job_by_jid(jid) [Sidekiq::ScheduledSet.new, Sidekiq::RetrySet.new].each do |set| set.each do |job| job.delete if job.jid == jid end end end end
2. 测试用例写法(以RSpec为例)
利用Sidekiq的测试模式模拟队列,或者mock依赖类来验证行为:
方式1:直接操作测试模拟队列
require 'sidekiq/testing' RSpec.describe Api::V1::Events::Destroy do before do Sidekiq::Testing.fake! # 启用Sidekiq测试模拟模式 end after do Sidekiq::Worker.clear_all # 测试后清理模拟队列 end it '成功删除关联的通知任务' do # 准备测试数据 test_jid = 'test-jid-12345' event = create(:event, notification_job_id: test_jid) # 往模拟队列里添加对应jid的任务 Sidekiq::Worker.jobs << { 'jid' => test_jid, 'class' => 'NotificationWorker' } # 执行服务 described_class.new(event: event).call # 验证任务已被移除 expect(Sidekiq::Worker.jobs.none? { |job| job['jid'] == test_jid }).to be true end end
方式2:mock任务管理类验证调用
这种方式不用操作真实队列,更专注于服务逻辑的正确性:
RSpec.describe Api::V1::Events::Destroy do it '调用任务管理类删除指定jid的任务' do test_jid = 'test-jid-12345' event = create(:event, notification_job_id: test_jid) # 创建mock对象 mock_manager = double(SidekiqTaskManager) # 预期会调用指定方法 expect(mock_manager).to receive(:delete_job_by_jid).with(test_jid) # 传入mock执行服务 described_class.new(event: event, sidekiq_task_manager: mock_manager).call end end
补充小提示
- Sidekiq 6.x+引入
Sidekiq::Job作为新的API规范,但测试模式下的模拟方法jobs仍然只挂载在Sidekiq::Worker上,所以测试时用Sidekiq::Worker.jobs才有效,生产环境绝对不能用这个方法。 - 如果你的任务可能在其他集合里(比如
DeadSet),可以把对应的集合也加到遍历列表里。
备注:内容来源于stack exchange,提问作者Максим Волков
相关产品推荐
相关产品推荐

