【问题标题】:How to test an ActiveJob callback?如何测试 ActiveJob 回调?
【发布时间】:2018-07-30 11:56:15
【问题描述】:

我正在为学生构建电子邮件提醒功能。他们应该在教师指定的时间间隔内收到一封电子邮件,说明他们取得了多大的进步。

我的方法是使用 ActiveJob 来安排发送电子邮件的作业,然后在执行作业时,我使用 after_perform 回调来安排下一个作业(取自 this post)。看起来是这样的:

class StudentReminderJob < ApplicationJob
  queue_as :default

  after_perform do |job|
    # (get user update frequency preference)

    self.class.set(wait: update_frequency).perform_later(job.arguments[0]) 
  end

  def perform(user)
    # Send email containing student stats information
    UserMailer.send_student_reminder_email(user)
  end
end

初始提醒集将由 rake 任务启动。

我的问题是:尝试使用perform_enqueued_jobs 测试上述代码时,我似乎遇到了无限循环。这是我的测试:

require 'rails_helper'

describe StudentReminderJob, active_job: true, type: :job do
  include ActiveJob::TestHelper
  include ActiveSupport::Testing::TimeHelpers

  let(:user) { create :user }
  subject(:job) { described_class.perform_later(user) }

  it 'gets enqueued' do
    # Passes
    expect { job }.to have_enqueued_job(described_class)
  end

  it 'receives perform later with user and base URL' do
    # Passes (may be duplicatative)
    expect(StudentReminderJob).to receive(:perform_later).with(user)
    job
  end

  it 'calls UserMailer with #send_student_reminder_email when performed' do
    # Fails - (SystemStackError: stack level too deep)
    expect(UserMailer).to receive(:send_student_reminder_email)
    perform_enqueued_jobs { job }
  end

  after do
    clear_enqueued_jobs
    clear_performed_jobs
  end
end

第三个 it 阻止失败并显示 SystemStackError: stack level too deep。有没有办法只执行队列中的一项作业而避免从回调中执行作业?

理想情况下,我想扩大我的测试范围,以确保在给定的日期和时间将新作业加入队列。

【问题讨论】:

  • 这不是最好的方法。当你的工人崩溃时会发生什么?你如何发现工人已经崩溃并且你必须重新启动它?我建议使用基于 cron 的调度程序,每 1m\1h\1d 运行一次(取决于您的电子邮件的频率)并存储在最后一封电子邮件的 db 时间戳中,以便您可以计算下一封。
  • @Avdept 感谢您的回复!我考虑使用像 Heroku 调度程序这样的基于 cron 的东西,但是电子邮件频率会有很大差异(想想几千个具有不同通知时间表的教室)。此外,用户的首选项控制器需要能够在收到电子邮件时进行更新,因此我需要能够以编程方式从应用程序控制器设置发送频率。

标签: ruby-on-rails rspec delayed-job rails-activejob


【解决方案1】:

我怀疑这里的问题是 perform_enqueued_jobs 助手尝试执行队列中的所有作业,直到该点,但队列列表永远不会用完,因为每个作业排队另一个作业(因此循环)。

试试这个,

  1. 首先,使用perform_later 将作业放入队列中。
  2. 从队列currently_queued_job = enqueued_jobs.first获取作业详细信息。
  3. 调用perform_enqueued_jobs,但使用:only 选项过滤/限制将要执行的作业。例如,
perform_enqueued_jobs(
  only: ->(job) { job['job_id'] == currently_queued_job['job_id'] }
)

这是perform_enqueued_jobs 的Rails documentation,提及:

:only:except 选项接受 Class、Array of Class 或 Proc。当传递一个 Proc 时,作业的一个实例将作为参数传递。

【讨论】:

    猜你喜欢
    • 2017-04-21
    • 2015-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-18
    • 1970-01-01
    • 2015-06-18
    相关资源
    最近更新 更多