首页 文章

如何使用rspec测试ActionMailer deliver_later

提问于
浏览
48

尝试使用delayed_job_active_record升级到Rails 4.2 . 我没有为测试环境设置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))

现在我使用它会出错 . (Double“mailer”收到意外消息:deliver_later with(no args))

只是

expect(ServiceMailer).to receive(:new_user)

nil的'undefined方法`deliver_later'也失败了:NilClass'

我已经尝试了一些示例,允许您查看作业是否在ActiveJob中使用test_helper排队,但我还没有设法测试正确的作业是否排队 .

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

如果包含test_helper,则会通过,但它不允许我检查它是否是正在发送的正确电子邮件 .

我想做的是:

  • 测试正确的电子邮件是否已排队(或在测试环境中立即执行)

  • 使用正确的参数(@user)

有任何想法吗??谢谢

11 回答

  • 3

    如果我理解正确,你可以这样做:

    message_delivery = instance_double(ActionMailer::MessageDelivery)
    expect(ServiceMailer).to receive(:new_user).with(@user).and_return(message_delivery)
    allow(message_delivery).to receive(:deliver_later)
    

    关键是你需要以某种方式为 deliver_later 提供双倍 .

  • 7

    如果你发现这个问题但是使用的是ActiveJob而不是简单的DelayedJob,并且正在使用Rails 5,我建议在 config/environments/test.rb 中配置ActionMailer:

    config.active_job.queue_adapter = :inline
    

    (这是Rails 5之前的默认行为)

  • 1

    使用ActiveJob和rspec 3.4你可以像这样使用have_enqueued_job

    expect {
      YourMailer.your_method.deliver_later
    }.to have_enqueued_job.on_queue('mailers')
    
  • 0

    添加这个:

    # spec/support/message_delivery.rb
    class ActionMailer::MessageDelivery
      def deliver_later
        deliver_now
      end
    end
    

    参考:http://mrlab.sk/testing-email-delivery-with-deliver-later.html

  • 64

    一个更好的解决方案(比monkeypatching deliver_later )是:

    require 'spec_helper'
    include ActiveJob::TestHelper
    
    describe YourObject do
      around { |example| perform_enqueued_jobs(&example) }
    
      it "sends an email" do
        expect { something_that.sends_an_email }.to change(ActionMailer::Base.deliveries, :length)
      end
    end
    

    around { |example| perform_enqueued_jobs(&example) } 确保在检查测试值之前运行后台任务 .

  • 9

    我会加上我的答案,因为其他人对我来说都不够好:

    1)没有必要模仿Mailer:Rails基本上已经为你做了 .

    2)没有必要真正触发电子邮件的创建:这将耗费时间并减慢您的测试速度!

    这就是为什么在 environments/test.rb 中你应该设置以下选项:

    config.action_mailer.delivery_method = :test
    config.active_job.queue_adapter = :test
    

    再次:不要使用 deliver_now 发送电子邮件,但 always 使用 deliver_later . 这会阻止您的用户等待有效发送电子邮件 . 如果您没有 sidekiqsucker_punch 或其他正在制作的产品,只需使用 config.active_job.queue_adapter = :async 即可 . 并且 asyncinline 用于开发环境 .

    给定测试环境的以下配置,您的电子邮件将始终排队并且从不执行以进行传递:这可以防止您模拟它们,并且可以检查它们是否正确排队 .

    在您的测试中, always 将测试分成两部分:1)一个单元测试,以检查电子邮件是否正确排队并使用正确的参数2)一个单元测试邮件,以检查主题,发件人,接收者和内容是否正确 .

    鉴于以下情况:

    class User
      after_update :send_email
    
      def send_email
        ReportMailer.update_mail(id).deliver_later
      end
    end
    

    编写测试以检查电子邮件是否正确排队:

    include ActiveJob::TestHelper
    expect { user.update(name: 'Hello') }.to have_enqueued_job(ActionMailer::DeliveryJob).with('ReportMailer', 'update_mail', 'deliver_now', user.id)
    

    并为您的电子邮件单独测试

    Rspec.describe ReportMailer do
        describe '#update_email' do
          subject(:mailer) { described_class.update_email(user.id) }
          it { expect(mailer.subject).to eq 'whatever' }
          ...
        end
    end
    
    • 您已经测试了您的电子邮件已经排队但不是通用作业 .

    • 你的测试很快

    • 你不需要嘲笑

    当您编写系统测试时,请随意决定是否要真正在那里发送电子邮件,因为速度不再那么重要了 . 我个人喜欢配置以下内容:

    RSpec.configure do |config|
      config.around(:each, :mailer) do |example|
        perform_enqueued_jobs do
          example.run
        end
      end
    end
    

    并将 :mailer 属性分配给我想要实际发送电子邮件的测试 .

    有关如何在Rails中正确配置电子邮件的更多信息,请阅读以下文章:https://medium.com/@coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd

  • 29

    我带着同样的怀疑,以一种不那么冗长(单行)的方式解决了this answer

    expect(ServiceMailer).to receive_message_chain(:new_user, :deliver_later).with(@user).with(no_args)
    

    请注意,最后 with(no_args) 是必不可少的 .

    但是,如果您不打扰 deliver_later 被调用,只需执行:

    expect(ServiceMailer).to expect(:new_user).with(@user).and_call_original

  • 0

    一个简单的方法是:

    expect(ServiceMailer).to(
      receive(:new_user).with(@user).and_call_original
    )
    # subject
    
  • 7

    我来这里寻找完整测试的答案,所以, not just 询问是否有一封邮件等待发送,此外,还有其收件人,主题......等

    我有一个解决方案,而不是来自here,但有一点变化:

    正如它所说,curial部分是

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

    问题是,在这种情况下,参数比邮件收到的参数不同于 生产环境 中接收的参数,如果第一个参数是模型,现在在测试中会收到一个哈希,所以会崩溃

    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

  • -1

    这个答案有点不同,但可能会有所帮助,例如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 .

    我更喜欢这样做,因为我更喜欢控制我拥有的依赖项 . 这是我的一个例子"Dependency inversion principle" .

    我不确定这是不是你的口味,但是另一种解决问题的方法=) .

  • 25

    我发现我的RSpec foo不能胜任测量“deliver_later”的任务 .

    对于20秒“完成”的解决方案,我选择了:

    MyMailer.send(ENV['RAILS_ENV'] == 'test' ? :deliver : :deliver_later)
    

相关问题