You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用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,提问作者Максим Волков

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.16 11:44:33