如何使用rspec测试ActionMailer的deliver_later方法

90

我正在尝试升级到Rails 4.2,并使用delayed_job_active_record。我没有为测试环境设置delayed_job后端,因为我认为这样工作任务将会立即执行。

我正在尝试使用RSpec测试新的“deliver_later”方法,但是我不确定该如何操作。

旧的控制器代码:

ServiceMailer.delay.new_user(@user)

新的控制器代码:

ServiceMailer.new_user(@user).deliver_later

我曾这样测试它:

expect(ServiceMailer).to receive(:new_user).with(@user).and_return(double("mailer", :deliver => true))

现在我使用那个会出错。(接收到意外的消息“deliver_later”,但没有参数)

只是

expect(ServiceMailer).to receive(:new_user)

出现问题:'undefined method `deliver_later' for nil:NilClass'

我尝试了一些例子,使用ActiveJob的test_helper来确认任务是否已经入队,但我没有成功测试到正确的任务已经入队。

expect(enqueued_jobs.size).to eq(1)

如果包含test_helper,此测试可以通过,但我无法检查发送的是否是正确的电子邮件。

我的目标是:

  • 测试队列中是否有正确的电子邮件(在测试环境中立即执行)
  • 使用正确的参数(@user)

有什么建议吗? 谢谢


14个回答

2
我认为测试这个的更好方法之一是检查作业状态,以及基本的响应JSON检查,例如:
expect(ActionMailer::MailDeliveryJob).to have_been_enqueued.on_queue('mailers').with('mailer_name', 'mailer_method', 'delivery_now', { :params => {}, :args=>[] } )

0

我来这里是为了寻找一个完整的测试答案,所以不仅仅是询问是否有一封待发送的邮件,还包括收件人、主题等等。

我有一个解决方案,它来自于这里,但稍作修改:

正如它所说,关键部分是

mail = perform_enqueued_jobs { ActionMailer::DeliveryJob.perform_now(*enqueued_jobs.first[:args]) }

问题是邮件程序接收的参数与生产环境中接收的参数不同。在生产环境中,如果第一个参数是 Model,则在测试中会接收一个哈希值,从而导致崩溃。
enqueued_jobs.first[:args]
["UserMailer", "welcome_email", "deliver_now", {"_aj_globalid"=>"gid://forjartistica/User/1"}]

因此,如果我们将邮件发送器称为UserMailer.welcome_email(@user).deliver_later,则在生产中,邮件发送器将接收一个用户,但在测试中将接收{"_aj_globalid"=>"gid://forjartistica/User/1"}

所有评论都将受到赞赏, 我找到的最不痛苦的解决方案是改变调用邮件发送器的方式,传递模型的ID而不是模型本身:

UserMailer.welcome_email(@user.id).deliver_later


0
在 rspec-rails 6 中,您可以这样操作:
it 'sends an email' do
  expect {
    do_stuff
  }.to have_enqueued_mail(ServiceMailer, :new_user)
    .with(an_instance_of(User))
end

这里是文档


0

这个答案有点不同,但可能有助于处理Rails API的新更改或者你想要传递方式的更改(例如使用deliver_now而不是deliver_later)。

我大多数时候做的是将邮件发送器作为方法的依赖项进行测试,但我不会从Rails中传递邮件发送器,而是传递一个对象,该对象将以“我想要的方式”执行任务...

例如,如果我想检查在用户注册后是否发送了正确的邮件... 我可以这样做...

class DummyMailer
  def self.send_welcome_message(user)
  end
end

it "sends a welcome email" do
  allow(store).to receive(:create).and_return(user)
  expect(mailer).to receive(:send_welcome_message).with(user)
  register_user(params, store, mailer)
end

然后在控制器中,我将调用该方法,并编写该邮件发送程序的“真正”实现...

class RegistrationsController < ApplicationController
  def create
    Registrations.register_user(params[:user], User, Mailer)
    # ...
  end

  class Mailer
    def self.send_welcome_message(user)
      ServiceMailer.new_user(user).deliver_later
    end
  end
end

这样做,我感觉我正在测试我是否发送了正确的消息到正确的对象,并带有正确的数据(参数)。我只需要创建一个非常简单的对象,没有逻辑,只需知道如何调用ActionMailer即可。

我更喜欢这样做,因为我更喜欢对我的依赖项有更多的控制。这对我来说是"依赖反转原则"的一个例子。

我不确定这是否符合您的口味,但这是解决问题的另一种方式=)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接