Как протестировать ActionMailer deliver_later с помощью rspec
пытается выполнить обновление до Rails 4.2, используя delayed_job_active_record. Я не устанавливал backend_job backend для тестовой среды, так как думал, что задания будут выполняться сразу же.
Я пытаюсь протестировать новый метод "deliver_later" с помощью Rspec, но я не уверен, как это сделать.
Старый код контроллера:
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 `deliver_later 'для nil: NilClass
Я пробовал некоторые примеры, которые позволяют вам видеть, заданы ли задания с помощью test_helper в ActiveJob, но мне не удалось проверить, что правильная работа поставлена в очередь.
expect(enqueued_jobs.size).to eq(1)
Это передается, если включен test_helper, но он не позволяет мне проверять, отправляется ли оно правильно.
Что я хочу сделать:
- проверьте, что правильный адрес электронной почты поставлен в очередь (или выполняется сразу в тестовом env).
- с правильными параметрами (@user)
Любые идеи?
спасибо
Ответы
Ответ 1
Если вы правильно поняли, вы могли бы сделать:
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)
Главное, что вам нужно как-то предоставить double для deliver_later
.
Ответ 2
Если вы нашли этот вопрос, но используете ActiveJob, а не просто DelayedJob самостоятельно, и используете Rails 5, я рекомендую настроить ActionMailer в config/environments/test.rb
:
config.active_job.queue_adapter = :inline
(это было поведение по умолчанию до Rails 5)
Ответ 3
Используя ActiveJob и rspec 3. 4+ вы можете использовать have_enqueued_job
следующим образом:
expect {
YourMailer.your_method.deliver_later
}.to have_enqueued_job.on_queue('mailers')
Ответ 4
Я добавлю свой ответ, потому что ни один из остальных не был достаточно хорош для меня:
1) Нет необходимости издеваться над Mailer: Rails в основном делает это уже для вас.
2) Нет необходимости действительно инициировать создание электронного письма: это потребует времени и замедлит ваш тест!
Поэтому в environments/test.rb
должны быть установлены следующие параметры:
config.action_mailer.delivery_method = :test
config.active_job.queue_adapter = :test
Опять же: не доставляйте свои электронные письма, используя deliver_now
, но всегда используйте deliver_later
. Это мешает вашим пользователям ждать эффективной доставки электронной почты. Если у вас нет sidekiq
, sucker_punch
или чего-либо другого в производстве, просто используйте config.active_job.queue_adapter = :async
. И async
, или inline
для среды разработки.
Принимая во внимание следующую конфигурацию для среды тестирования, ваши электронные письма всегда будут помещены в очередь и никогда не будут выполнены для доставки: это предотвращает их насмешку, и вы можете проверить, правильно ли они поставлены в очередь.
В ваших тестах всегда разделяет тест на две части:
1) Один unit тест, чтобы проверить, что электронная почта поставлена в очередь правильно и с правильными параметрами
2) Один unit тест для почты, чтобы проверить правильность темы, отправителя, получателя и содержимого.
Учитывая следующий сценарий:
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
Ответ 5
Добавьте это:
# 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
Ответ 6
Более приятное решение (чем 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) }
обеспечивает выполнение фоновых задач перед проверкой тестовых значений.
Ответ 7
Я пришел с такими же сомнениями и решил в менее подробном (однострочном) способе, вдохновленном этим ответом
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
Ответ 8
Простой способ:
expect(ServiceMailer).to(
receive(:new_user).with(@user).and_call_original
)
# subject
Ответ 9
Я обнаружил, что мой RSpec foo не справился с задачей измерения "deliver_later".
В течение 20 секунд "get" er done "я пошел с:
MyMailer.send(ENV['RAILS_ENV'] == 'test' ? :deliver : :deliver_later)
Ответ 10
Я пришел сюда, чтобы найти ответ для полного тестирования, поэтому не просто спрашивает, есть ли почта, ожидающая отправки, кроме того, для ее получатель, субъект... и т.д.
У меня есть решение, чем из здесь, но с небольшим изменением:
Как говорится, кураторская часть
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"}
Все комментарии будут оценены,
Менее болезненное решение, которое я нашел, изменяет способ, которым я называю почтовые программы, прохождение, идентификатор модели, а не модель:
UserMailer.welcome_email(@user.id).deliver_later
Ответ 11
Этот ответ немного отличается, но может помочь в таких случаях, как новое изменение в API рельсов или изменение способа доставки (например, используйте deliver_now
вместо deliver_later
).
В большинстве случаев я передаю почтовую программу в качестве зависимости от метода, который я тестирую, но я не передаю почтовую программу из рельсов, вместо этого я передаю объект, который будет выполнять действия "таким образом, чтобы Я хочу "...
Например, если я хочу проверить, что я отправляю правильное письмо после регистрации пользователя... я мог бы сделать...
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 хочет вызываться.
Я предпочитаю делать это, потому что я предпочитаю иметь больше контроля над моими зависимостями. Это пример для меня "принципа обращения зависимостей".
Я не уверен, если это ваш вкус, но это еще один способ решения проблемы =).
Ответ 12
Этот ответ предназначен для Rails Test, а не для rspec...
Если вы используете 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
Вы можете проверить в своем тесте, было ли письмо добавлено в очередь:
# 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
Если вы сделаете это, вы будете удивлены неудачным тестом, который сообщает, что assert_enqueued_jobs не определен для использования нами.
Это связано с тем, что наш тест наследуется от ActionController :: TestCase, который на момент написания не включал ActiveJob :: TestHelper.
Но мы можем быстро это исправить:
# test/test_helper.rb
class ActionController::TestCase
include ActiveJob::TestHelper
…
end
Ссылка:https://www.engineyard.com/blog/testing-async-emails-rails-42