Ruby-on-rails 如何使用 rspec 测试 ActionMailer Deliver_later

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27647749/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 00:01:13  来源:igfitidea点击:

How to test ActionMailer deliver_later with rspec

ruby-on-railsrspecactionmailerdelayed-jobrails-activejob

提问by bobomoreno

trying to upgrade to Rails 4.2, using delayed_job_active_record. I've not set the delayed_job backend for test environment as thought that way jobs would execute straight away.

尝试使用 delay_job_active_record 升级到 Rails 4.2。我没有为测试环境设置 delay_job 后端,因为我认为作业会立即执行。

I'm trying to test the new 'deliver_later' method with Rspec, but I'm not sure how.

我正在尝试使用 Rspec 测试新的“deliver_later”方法,但我不确定如何。

Old controller code:

旧控制器代码:

ServiceMailer.delay.new_user(@user)

New controller code:

新的控制器代码:

ServiceMailer.new_user(@user).deliver_later

I USED to test it like so:

我曾经像这样测试它:

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

Now I get errors using that. (Double "mailer" received unexpected message :deliver_later with (no args))

现在我使用它遇到错误。(双重“邮件程序”收到意外消息:deliver_later with (no args))

Just

只是

expect(ServiceMailer).to receive(:new_user)

fails too with 'undefined method `deliver_later' for nil:NilClass'

对于 nil:NilClass 的“未定义方法‘deliver_later’也失败了

I've tried some examples that allow you to see if jobs are enqueued using test_helper in ActiveJob but I haven't managed to test that the correct job is queued.

我已经尝试了一些示例,这些示例可以让您查看是否使用 ActiveJob 中的 test_helper 将作业排入队列,但我还没有设法测试是否将正确的作业排入队列。

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

This passes if the test_helper is included, but it doesn't allow me to check it is the correct email that is being sent.

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

What I want to do is:

我想做的是:

  • test that the correct email is queued (or executed straight away in test env)
  • with the correct parameters (@user)
  • 测试正确的电子邮件是否已排队(或在测试环境中立即执行)
  • 使用正确的参数(@user)

Any ideas?? thanks

有任何想法吗??谢谢

回答by Peter Alfvin

If I understand you correctly, you could do:

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

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)

The key thing is that you need to somehow provide a double for deliver_later.

关键是你需要以某种方式为deliver_later.

回答by Alter Lagos

Using ActiveJob and rspec-rails3.4+, you could use have_enqueued_joblike this:

使用 ActiveJob 和rspec-rails3.4+,你可以这样使用have_enqueued_job

expect { 
  YourMailer.your_method.deliver_later 
  # or any other method that eventually would trigger mail enqueuing
}.to( 
  have_enqueued_job.on_queue('mailers').with(
    # `with` isn't mandatory, but it will help if you want to make sure is
    # the correct enqueued mail.
    'YourMailer', 'your_method', 'deliver_now', any_param_you_want_to_check
  )
)

also double check in config/environments/test.rbyou have:

还要仔细检查config/environments/test.rb你有:

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

Another option would be to run inline jobs:

另一种选择是运行内联作业:

config.active_job.queue_adapter = :inline

But keep in mind this would affect the overall performanceof your test suite, as all your jobswill run as soon as they're enqueued.

但请记住,这会影响您的测试套件的整体性能,因为您的所有作业将在入队后立即运行。

回答by Gabe Kopley

If you find this question but are using ActiveJob rather than simply DelayedJob on its own, and are using Rails 5, I recommend configuring ActionMailer in config/environments/test.rb:

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

config.active_job.queue_adapter = :inline

(this was the default behavior prior to Rails 5)

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

回答by coorasse

I will add my answer because none of the others was good enough for me:

我将添加我的答案,因为其他答案都不适合我:

1) There is no need to mock the Mailer: Rails basically does that already for you.

1) 无需模拟 Mailer:Rails 基本上已经为您做到了。

2) There is no need to really trigger the creation of the email: this will consume time and slow down your test!

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

That's why in environments/test.rbyou should have the following options set:

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

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

Again: don't deliver your emails using deliver_nowbut alwaysuse deliver_later. That prevents your users from waiting for the effective delivering of the email. If you don't have sidekiq, sucker_punch, or any other in production, simply use config.active_job.queue_adapter = :async. And either asyncor inlinefor development environment.

再次强调:不要使用deliver_now始终使用deliver_later. 这可以防止您的用户等待电子邮件的有效传递。如果你没有sidekiqsucker_punch或任何其他在生产中,简单地使用config.active_job.queue_adapter = :async。,要么async还是inline发展环境。

Given the following configuration for the testing environment, you emails will always be enqueued and never executed for delivery: this prevents your from mocking them and you can check that they are enqueued correctly.

鉴于测试环境的以下配置,您的电子邮件将始终排队并且永远不会执行交付:这可以防止您模拟它们,您可以检查它们是否正确排队。

In you tests, alwayssplit the test in two: 1) One unit test to check that the email is enqueued correctly and with the correct parameters 2) One unit test for the mail to check that the subject, sender, receiver and content are correct.

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

Given the following scenario:

鉴于以下场景:

class User
  after_update :send_email

  def send_email
    ReportMailer.update_mail(id).deliver_later
  end
end

Write a test to check the email is enqueued correctly:

编写一个测试来检查电子邮件是否正确入队:

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

and write a separate test for your email

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

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
  • You have tested exactly that your email has been enqueued and not a generic job.
  • Your test is fast
  • You needed no mocking
  • 您已经准确地测试过您的电子邮件已入队,而不是一般作业。
  • 你的测试很快
  • 你不需要嘲笑

When you write a system test, feel free to decide if you want to really deliver emails there, since speed doesn't matter that much anymore. I personally like to configure the following:

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

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

and assign the :mailerattribute to the tests were I want to actually send emails.

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

For more about how to correctly configure your email in Rails read this article: https://medium.com/@coorasse/the-correct-emails-configuration-in-rails-c1d8418c0bfd

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

回答by Minimul

Add this:

添加这个:

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

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

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

回答by Qqwy

A nicer solution (than monkeypatching deliver_later) is:

一个更好的解决方案(比 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

The around { |example| perform_enqueued_jobs(&example) }ensures that background tasks are run before checking the test values.

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

回答by Nuno Silva

A simple way is:

一个简单的方法是:

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

回答by Luccas

I came with the same doubt and resolved in a less verbose (single line) way inspired by this answer

我带着同样的疑问来了,并在此答案的启发下以不那么冗长(单行)的方式解决了

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

Note that the last with(no_args)is essential.

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

But, if you don't bother if deliver_lateris being called, just do:

但是,如果您不介意deliver_later被调用,请执行以下操作:

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

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

回答by crowns4days

For recent Googlers:

对于最近的 Google 员工:

allow(YourMailer).to receive(:mailer_method).and_call_original

expect(YourMailer).to have_received(:mailer_method)

回答by maurymmarques

This answer is for Rails Test, not for rspec...

这个答案是针对 Rails 测试的,而不是针对 rspec ...

If you are using delivery_laterlike this:

如果您delivery_later像这样使用:

# app/controllers/users_controller.rb 

class UsersController < ApplicationController 
  … 
  def create 
    … 
    # Yes, Ruby 2.0+ keyword arguments are preferred 
    UserMailer.welcome_email(user: @user).deliver_later 
  end 
end 

You can check in your test if the email has been added to the queue:

如果电子邮件已添加到队列中,您可以检查您的测试:

# test/controllers/users_controller_test.rb 

require 'test_helper' 

class UsersControllerTest < ActionController::TestCase 
  … 
  test 'email is enqueued to be delivered later' do 
    assert_enqueued_jobs 1 do 
      post :create, {…} 
    end 
  end 
end 

If you do this though, you'll surprised by the failing test that tells you assert_enqueued_jobs is not defined for us to use.

如果你这样做,你会惊讶于失败的测试告诉你 assert_enqueued_jobs 不是为我们定义的。

This is because our test inherits from ActionController::TestCase which, at the time of writing, does not include ActiveJob::TestHelper.

这是因为我们的测试继承自 ActionController::TestCase,在撰写本文时,它不包括 ActiveJob::TestHelper。

But we can quickly fix this:

但是我们可以快速解决这个问题:

# test/test_helper.rb 

class ActionController::TestCase 
  include ActiveJob::TestHelper 
  … 
end 

Reference: https://www.engineyard.com/blog/testing-async-emails-rails-42

参考:https: //www.engineyard.com/blog/testing-async-emails-rails-42